JPA 기술면접 Q&A

Jihye Gim·2026년 5월 20일

Codeit SB11

목록 보기
20/22

JPA 기술면접 대비 Q&A 20문제

Q1. JPA란 무엇인가요?

A.

JPA(Java Persistence API)는 자바 진영의 ORM(Object-Relational Mapping) 표준 명세(인터페이스) 입니다.

핵심 포인트는 JPA는 인터페이스이고, 실제 구현체는 Hibernate, EclipseLink 등이 있다는 점입니다. Spring Boot에서는 기본적으로 Hibernate를 사용합니다.

JPA를 사용하면 SQL을 직접 작성하지 않고 자바 객체와 DB 테이블을 매핑해 데이터를 다룰 수 있습니다.

// JPA 없이 (JDBC)
String sql = "INSERT INTO member (name, age) VALUES (?, ?)";
pstmt.setString(1, member.getName());
pstmt.setInt(2, member.getAge());

// JPA 사용
em.persist(member); // SQL 자동 생성

Q2. ORM이란 무엇이고 장단점은?

A.

ORM(Object-Relational Mapping)은 객체와 관계형 DB 테이블을 자동으로 매핑해주는 기술입니다.

장점

  • SQL을 직접 작성하지 않아도 되어 생산성 향상
  • 특정 DB에 종속되지 않아 DB 교체가 용이 (방언 설정만 변경)
  • 패러다임 불일치 해소 — 상속, 연관관계 등을 객체 중심으로 처리
  • 유지보수 용이 — 필드 추가 시 SQL 전체 수정 불필요

단점

  • 복잡한 쿼리(통계, 집계, 대용량 배치)는 직접 SQL이 더 효율적
  • 잘못 사용하면 N+1 문제 등 성능 문제 발생
  • 학습 곡선 존재

Q3. 영속성 컨텍스트(Persistence Context)란?

A.

영속성 컨텍스트는 엔티티를 영구 저장하는 환경입니다. EntityManager가 관리하며, 트랜잭션 단위로 생성·소멸됩니다.

핵심 기능은 5가지입니다.

기능설명
1차 캐시같은 트랜잭션 내 동일 PK 조회 시 DB 접근 없이 캐시 반환
동일성 보장같은 PK의 엔티티는 == 비교 시 true
쓰기 지연트랜잭션 커밋 시점에 INSERT/UPDATE SQL 일괄 전송
변경 감지(Dirty Checking)스냅샷과 비교해 변경된 필드를 자동으로 UPDATE
지연 로딩실제 사용 시점에 SQL 실행
Member member = em.find(Member.class, 1L); // DB 조회 후 1차 캐시 저장
Member member2 = em.find(Member.class, 1L); // 1차 캐시에서 반환 (SQL 미발생)
System.out.println(member == member2); // true (동일성 보장)

Q4. 엔티티의 생명주기(Life Cycle) 4가지 상태를 설명해주세요.

A.

상태설명예시
비영속(Transient)영속성 컨텍스트와 무관한 새 객체new Member()
영속(Managed)영속성 컨텍스트에서 관리되는 상태em.persist(member)
준영속(Detached)영속성 컨텍스트에서 분리된 상태em.detach(member)
삭제(Removed)삭제 예약 상태em.remove(member)
비영속 → (persist) → 영속 → (detach/close) → 준영속
                              ↓ (remove)
                            삭제

영속 상태일 때만 변경 감지, 1차 캐시, 쓰기 지연이 동작합니다.


Q5. 1차 캐시와 2차 캐시의 차이를 설명해주세요.

A.

1차 캐시

  • 영속성 컨텍스트 내부에 존재
  • 트랜잭션 단위로 생성·소멸 (트랜잭션 종료 시 캐시도 사라짐)
  • 같은 트랜잭션 내 동일 PK 엔티티를 반복 조회해도 DB를 1번만 접근
  • 별도 설정 없이 기본으로 동작

2차 캐시 (공유 캐시)

  • 애플리케이션 전체에서 공유하는 캐시
  • EntityManagerFactory 수준에서 관리
  • 트랜잭션이 달라도 캐시 공유 가능
  • Ehcache, Caffeine 등 별도 캐시 라이브러리 설정 필요
  • @Cacheable 어노테이션으로 엔티티에 적용
@Entity
@Cacheable // 2차 캐시 적용
@Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
public class Member { ... }

Q6. 변경 감지(Dirty Checking)란 무엇인가요?

A.

변경 감지는 영속 상태의 엔티티를 별도의 update() 호출 없이 자동으로 UPDATE 해주는 JPA 기능입니다.

동작 원리

  1. 엔티티가 영속 상태가 될 때 스냅샷(원본 상태)을 저장
  2. 트랜잭션 커밋 시점에 현재 엔티티와 스냅샷을 비교
  3. 변경된 필드가 있으면 UPDATE SQL 자동 생성·실행
@Transactional
public void updateMember(Long id, String name) {
    Member member = memberRepository.findById(id).orElseThrow();
    member.setName(name); // 변경만 하면 자동으로 UPDATE 실행
    // save() 호출 불필요
}

Q7. 지연 로딩(Lazy Loading)과 즉시 로딩(Eager Loading)의 차이를 설명해주세요.

A.

구분지연 로딩 (LAZY)즉시 로딩 (EAGER)
시점연관 엔티티 실제 사용 시엔티티 조회 시 연관 엔티티 함께 조회
SQL사용 시점에 추가 SELECT 발생JOIN 등으로 한 번에 조회
권장✅ 권장❌ 비권장
@Entity
public class Order {
    @ManyToOne(fetch = FetchType.LAZY) // 지연 로딩 (권장)
    private Member member;
}

즉시 로딩의 문제점

  • 예상치 못한 SQL이 발생
  • JPQL에서 N+1 문제 발생 가능
  • 불필요한 데이터까지 항상 조회

Q8. N+1 문제란 무엇이고 어떻게 해결하나요?

A.

N+1 문제는 1개의 메인 쿼리 실행 후, 연관 엔티티를 가져오기 위해 N개의 추가 쿼리가 발생하는 성능 문제입니다.

발생 예시

List<Member> members = memberRepository.findAll(); // SELECT * FROM member (1번)
for (Member member : members) {
    member.getOrders().size(); // 각 member마다 SELECT * FROM orders (N번)
}
// 총 1 + N번의 쿼리 발생

해결 방법

  1. Fetch Join — 가장 일반적인 방법
@Query("SELECT m FROM Member m JOIN FETCH m.orders")
List<Member> findAllWithOrders();
  1. @EntityGraph — 어노테이션으로 간편하게
@EntityGraph(attributePaths = {"orders"})
List<Member> findAll();
  1. Batch Size — 컬렉션 페이징 시 유용 (application.yml)
spring:
  jpa:
    properties:
      hibernate:
        default_batch_fetch_size: 100
  1. DTO 직접 조회 — 필요한 데이터만 JPQL로 프로젝션

Q9. fetch join이란 무엇인가요?

A.

fetch join은 JPQL에서만 사용할 수 있는 문법으로, 연관된 엔티티나 컬렉션을 SQL 1번으로 함께 조회하는 방법입니다.

// 일반 join: member만 조회 (team은 LAZY)
SELECT m FROM Member m JOIN m.team t

// fetch join: member와 team 함께 조회
SELECT m FROM Member m JOIN FETCH m.team t

주의사항

  • 컬렉션(일대다) fetch join 시 페이징 불가 — Hibernate가 메모리에서 페이징 처리 (경고 발생)
  • 둘 이상의 컬렉션 fetch join 시 데이터 중복 발생 가능 → DISTINCT 또는 @BatchSize 사용
  • 별칭(alias) 사용 자제 — 연관된 데이터의 일부만 조작하면 영속성 컨텍스트가 오염될 수 있음

Q10. @OneToMany, @ManyToOne, @ManyToMany 연관관계를 설명해주세요.

A.

어노테이션관계예시
@OneToMany1:N팀(1) : 멤버(N) — 팀 입장에서
@ManyToOneN:1멤버(N) : 팀(1) — 멤버 입장에서
@OneToOne1:1회원(1) : 회원 상세정보(1)
@ManyToManyM:N회원 : 상품

@ManyToMany는 실무에서 지양합니다.

M:N 관계는 중간 테이블에 추가 컬럼(등록일, 수량 등)을 넣을 수 없기 때문에, 중간 엔티티를 만들어 @OneToMany + @ManyToOne으로 풀어내는 것을 권장합니다.

// M:N 지양
Member ←@ManyToMany→ Product

// 권장: 중간 테이블을 엔티티로
Member ←@OneToMany→ MemberProduct ←@ManyToOne→ Product

Q11. 연관관계 주인(owning side)이란 무엇인가요?

A.

JPA에서 양방향 연관관계를 설정할 때 외래 키(FK)를 관리하는 쪽을 연관관계의 주인이라고 합니다.

  • 연관관계 주인: FK를 가진 테이블의 엔티티 → 등록, 수정 가능
  • 주인이 아닌 쪽: mappedBy 속성으로 지정 → 읽기만 가능

FK는 항상 N(Many) 쪽에 있으므로, 주인도 항상 @ManyToOne 쪽(N쪽)입니다.

@Entity
public class Member {
    @ManyToOne
    @JoinColumn(name = "team_id") // 연관관계 주인 (FK 관리)
    private Team team;
}

@Entity
public class Team {
    @OneToMany(mappedBy = "team") // 주인이 아님, 읽기 전용
    private List<Member> members = new ArrayList<>();
}

Q12. 영속성 전이(Cascade)와 고아 객체(orphanRemoval)를 설명해주세요.

A.

영속성 전이 (Cascade)

부모 엔티티의 상태 변화를 자식 엔티티에 전이시키는 기능입니다.

@OneToMany(mappedBy = "parent", cascade = CascadeType.ALL)
private List<Child> children = new ArrayList<>();
종류설명
ALL모든 전이
PERSIST저장 시 자식도 함께 저장
REMOVE삭제 시 자식도 함께 삭제
MERGE병합 시 자식도 함께 병합

고아 객체 제거 (orphanRemoval)

부모 엔티티와의 연관관계가 끊어진 자식 엔티티를 자동으로 삭제합니다.

@OneToMany(mappedBy = "parent", orphanRemoval = true)
private List<Child> children = new ArrayList<>();

// 컬렉션에서 제거하면 자동으로 DELETE
parent.getChildren().remove(child); // DELETE 발생

Q13. JPQL이란 무엇인가요?

A.

JPQL(Java Persistence Query Language)은 JPA에서 사용하는 객체 지향 쿼리 언어입니다.

SQL과 유사하지만 테이블과 컬럼명 대신 엔티티 클래스와 필드명을 사용합니다.

// SQL (테이블명, 컬럼명 사용)
SELECT * FROM member WHERE age > 20

// JPQL (엔티티명, 필드명 사용)
SELECT m FROM Member m WHERE m.age > 20

한계

  • 문자열이므로 컴파일 타임에 오류를 잡지 못함
  • 동적 쿼리 작성이 어려움
  • 오타가 런타임 에러로 이어짐

이런 단점을 보완하기 위해 QueryDSL을 함께 사용합니다.


Q14. QueryDSL이란 무엇이고 왜 사용하나요?

A.

QueryDSL은 타입 세이프(type-safe)한 쿼리 빌더 라이브러리입니다. 컴파일 시점에 Q-class를 생성하여 쿼리를 코드로 작성할 수 있습니다.

JPQL과의 비교

구분JPQLQueryDSL
타입 안전성❌ 문자열✅ 컴파일 타임 검사
자동완성❌ 불가✅ IDE 지원
동적 쿼리❌ 불편BooleanBuilder 등 편리
가독성보통높음
// JPQL
@Query("SELECT m FROM Member m WHERE m.age > :age AND m.name = :name")
List<Member> findByAgeAndName(int age, String name);

// QueryDSL
public List<Member> findByCondition(int age, String name) {
    return queryFactory
        .selectFrom(member)
        .where(
            member.age.gt(age),
            member.name.eq(name)
        )
        .fetch();
}

Q15. Spring Data JPA의 Repository 계층 구조를 설명해주세요.

A.

Spring Data JPA의 Repository 인터페이스 계층은 다음과 같습니다.

Repository (최상위 마커 인터페이스)
    └── CrudRepository (기본 CRUD)
            └── PagingAndSortingRepository (페이징, 정렬)
                    └── JpaRepository (JPA 특화 기능)

주로 사용하는 JpaRepository가 제공하는 기능

  • 기본 CRUD: save(), findById(), findAll(), delete()
  • 페이징·정렬: findAll(Pageable pageable)
  • 배치 삭제: deleteAllInBatch()
  • flush: flush(), saveAndFlush()
public interface MemberRepository extends JpaRepository<Member, Long> {
    // 메서드 이름으로 쿼리 자동 생성
    List<Member> findByNameAndAge(String name, int age);
    Optional<Member> findByEmail(String email);
    boolean existsByEmail(String email);
}

Q16. @Transactional과 JPA의 관계를 설명해주세요.

A.

JPA에서 데이터 변경은 반드시 트랜잭션 안에서 이루어져야 합니다. 트랜잭션 없이는 영속성 컨텍스트의 핵심 기능(변경 감지, 쓰기 지연)이 동작하지 않습니다.

@Transactional의 동작 원리

AOP 프록시를 통해 메서드 실행 전 트랜잭션 시작, 정상 종료 시 커밋, 예외 발생 시 롤백합니다.

@Service
@Transactional(readOnly = true) // 클래스 레벨: 기본 readOnly
public class MemberService {

    @Transactional // 메서드 레벨: 쓰기 트랜잭션
    public Member save(Member member) {
        return memberRepository.save(member);
    }

    public Member findById(Long id) { // readOnly 적용
        return memberRepository.findById(id).orElseThrow();
    }
}

readOnly = true 옵션의 이점

  • 변경 감지(Dirty Checking) 스킵 → 성능 향상
  • DB에 따라 읽기 전용 복제본(replica)으로 라우팅 가능
  • Flush 모드 NEVER → 불필요한 스냅샷 비교 생략

Q17. 낙관적 락(Optimistic Lock)과 비관적 락(Pessimistic Lock)의 차이를 설명해주세요.

A.

낙관적 락 (Optimistic Lock)

  • 충돌이 드물다고 가정
  • DB 락을 걸지 않고 @Version 필드로 충돌 감지
  • 업데이트 시 버전이 다르면 OptimisticLockException 발생
  • 성능이 좋고 읽기가 많은 서비스에 적합
@Entity
public class Product {
    @Version
    private int version; // 변경 시 자동으로 버전 증가
}

비관적 락 (Pessimistic Lock)

  • 충돌이 자주 발생한다고 가정
  • 조회 시 DB 레벨에서 락(SELECT FOR UPDATE) 획득
  • 다른 트랜잭션의 해당 행 접근 차단
  • 재고, 잔액 등 동시 수정이 잦고 정합성이 중요한 경우 사용
@Lock(LockModeType.PESSIMISTIC_WRITE)
@Query("SELECT p FROM Product p WHERE p.id = :id")
Optional<Product> findByIdWithLock(Long id);
구분낙관적 락비관적 락
성능높음낮음 (락 대기)
충돌 빈도낮을 때 적합높을 때 적합
구현@Version@Lock
예외OptimisticLockExceptionPessimisticLockingFailureException

Q18. JPA에서 상속 관계 매핑 전략 3가지를 설명해주세요.

A.

관계형 DB는 상속이 없으므로, 객체의 상속 구조를 테이블로 표현하는 3가지 전략이 있습니다.

1. 단일 테이블 전략 (SINGLE_TABLE) — 기본값

모든 데이터를 하나의 테이블에 저장. DTYPE 컬럼으로 구분.

  • ✅ 조회 성능 좋음 (JOIN 없음)
  • ❌ 자식 엔티티 컬럼은 전부 NULL 허용

2. 조인 전략 (JOINED)

부모·자식 각각 테이블을 생성하고 PK/FK로 JOIN.

  • ✅ 정규화, NULL 없음
  • ❌ 조회 시 JOIN 발생으로 성능 저하 가능

3. 구현 클래스별 테이블 전략 (TABLE_PER_CLASS)

부모 테이블 없이 자식 테이블마다 모든 컬럼 포함.

  • ❌ 부모 타입으로 조회 시 UNION ALL 발생
  • ❌ 권장하지 않음
@Entity
@Inheritance(strategy = InheritanceType.JOINED) // 전략 선택
@DiscriminatorColumn(name = "DTYPE")
public abstract class Item { ... }

@Entity
public class Album extends Item { ... }

Q19. 임베디드 타입(@Embeddable)이란 무엇인가요?

A.

임베디드 타입은 새로운 값 타입을 직접 정의해 엔티티의 필드로 사용하는 기능입니다. DB 테이블 구조는 그대로 유지하면서 객체를 의미있는 단위로 묶을 수 있습니다.

@Embeddable
public class Address {
    private String city;
    private String street;
    private String zipcode;
}

@Entity
public class Member {
    @Embedded
    private Address homeAddress; // 재사용 가능

    @Embedded
    @AttributeOverrides({ // 같은 타입을 2번 쓸 때 컬럼명 재정의
        @AttributeOverride(name = "city", column = @Column(name = "work_city"))
    })
    private Address workAddress;
}

장점

  • 코드 재사용성 향상
  • 높은 응집도 — 관련 필드와 메서드를 한 곳에 모음
  • 의미있는 도메인 표현 (homeAddress.getCity())

Q20. JPA와 MyBatis의 차이, 언제 무엇을 쓰나요?

A.

구분JPA (Hibernate)MyBatis
방식ORM (객체 ↔ 테이블 매핑)SQL Mapper (SQL ↔ 결과 매핑)
SQL 작성자동 생성직접 작성
생산성단순 CRUD에서 높음복잡한 쿼리에서 높음
학습 난이도높음 (N+1, 연관관계 등)낮음
동적 쿼리QueryDSL 필요XML로 편리하게 작성
성능 튜닝상대적으로 어려움SQL 직접 최적화 가능

언제 무엇을 쓰나요?

  • JPA 선택 — 도메인 모델이 복잡하고 객체 중심 설계가 중요할 때. 빠른 CRUD 개발이 필요할 때.
  • MyBatis 선택 — 통계, 집계, 복잡한 JOIN 쿼리가 많을 때. DBA와 협업이 많아 SQL을 직접 관리해야 할 때.
  • 혼용 — 실무에서는 JPA로 기본 CRUD를 처리하고, 복잡한 조회는 QueryDSL이나 Native Query를 활용하는 방식이 일반적입니다.

profile
Rookie

0개의 댓글