JPA 면접 대비

구성: 📋 기본 면접 Q&A(16문항) → 💡 추가 예상 질문 Q&A(14문항)

📋 기본 면접 Q&A 펼치기
Q1. JPA란 무엇이며 사용하는 이유는 무엇인가요?✅🟩🟩🟩🟩

JPA는 자바 진영에서 ORM 표준을 정의한 기술이고, 객체 지향적인 방식으로 데이터베이스를 다루기 위해 사용하는 것입니다.

먼저, JPA 자체는 라이브러리가 아니라 규격(인터페이스 표준)이고, 실제로는 Hibernate 같은 구현체를 통해 사용합니다. 핵심은 객체와 테이블을 매핑해서, 자바 객체를 다루면 내부에서 알아서 SQL을 만들어 주고 실행해 준다는 점입니다. 그래서 개발자는 비즈니스 로직에 더 집중하고, SQL과 JDBC 코드를 일일이 작성하는 양을 크게 줄일 수 있습니다.

JPA를 사용하는 이유는 몇 가지로 정리할 수 있습니다.
첫째, 객체 지향적인 도메인 모델을 그대로 유지하면서 DB 작업을 할 수 있다는 점입니다. 연관 관계나 상속 구조 같은 것을 엔티티로 자연스럽게 표현하고, JPA가 이를 테이블과 관계로 매핑해 줍니다.

둘째, 생산성과 유지보수성이 좋아집니다. 기본적인 CRUD는 메서드 호출만으로 처리할 수 있고, JPQL 같은 객체 중심 쿼리 언어를 제공해서 SQL을 직접 관리하는 부분이 줄어듭니다. 덕분에 코드가 단순해지고, 리팩터링이나 확장이 쉬워집니다.

셋째, 데이터베이스 의존성을 낮출 수 있습니다. JPA 표준에 맞춰 코드를 작성하면, 구현체나 DB가 바뀌어도 애플리케이션 코드 변경을 최소화할 수 있습니다.

또한 영속성 컨텍스트를 통해 1차 캐시, 변경 감지, 지연 로딩 같은 기능을 제공해서, 트랜잭션 범위 안에서 엔티티 상태를 일관성 있게 관리하고 성능 최적화에도 도움을 줍니다.

정리하면, JPA는 자바에서 ORM을 표준화한 기술이고, 객체 중심 모델을 유지하면서 SQL·JDBC 반복 작업을 줄이고, 유지보수성과 확장성을 높이기 위해 사용한다고 정리할 수 있습니다.

Q2. JPA, Hibernate, Spring Data JPA의 차이점을 설명해주세요.✅🟩🟩🟩🟩

JPA, Hibernate, Spring Data JPA는 표준 – 구현체 – 스프링 통합 모듈 이렇게 세 층으로 나뉜다고 정리할 수 있습니다

먼저 JPA는 자바 진영의 ORM 표준 명세입니다. 인터페이스와 어노테이션 같은 규칙만 정의해 두고, “엔티티를 이렇게 매핑해라, 영속성 컨텍스트는 이렇게 동작해야 한다” 같은 계약만 정해 둡니다. 그래서 JPA 자체는 동작하는 라이브러리가 아니라, 여러 구현체들이 따라야 하는 공통 약속에 가깝습니다.

그다음 Hibernate는 JPA를 실제로 구현한 ORM 프레임워크입니다. JPA가 정의한 인터페이스를 구현해서 실제로 SQL을 만들고 실행해 주는 역할을 하고, 거기에 더해 캐시, 배치 처리, 고유한 쿼리 언어 같은 추가 기능도 많이 제공합니다. JPA 없이 Hibernate만 단독으로 쓰는 것도 가능하지만, 실무에서는 보통 JPA 표준에 맞춰 Hibernate를 사용하는 방식이 많습니다.

마지막으로 Spring Data JPA는 스프링에서 JPA 사용을 더 편하게 감싸 주는 모듈입니다. Repository 인터페이스만 정의하면 기본 CRUD 구현을 자동으로 만들어 주고, 메서드 이름으로 쿼리를 생성한다든지, 페이징·정렬을 쉽게 처리하는 등 반복적인 JPA 코드를 크게 줄여 줍니다. 내부에서는 결국 JPA와 그 구현체인 Hibernate를 사용하지만, 개발자는 Spring Data JPA가 제공하는 추상화된 인터페이스만 다루게 됩니다.

정리하면, JPA는 표준, Hibernate는 그 표준을 구현한 ORM 프레임워크, Spring Data JPA는 이를 스프링스럽게 감싼 생산성 도구라고 설명드릴 수 있습니다. 실무에서는 보통 “Spring Data JPA + Hibernate(JPA 구현체)” 조합으로 사용합니다.

Q3. 영속성 컨텍스트(Persistence Context)란 무엇인가요?✅🟩🟩🟩🟩

영속성 컨텍스트는 JPA에서 엔티티를 저장하고 관리하는 1차 캐시이자, 엔티티의 생명주기를 관리하는 가상의 저장소라고 말씀드리겠습니다. 보통 하나의 트랜잭션 안에서 EntityManager가 들고 있는 엔티티 저장소를 영속성 컨텍스트라고 부릅니다.

조금 풀어서 말씀드리면, 영속성 컨텍스트는 DB에 바로 접근하는 것이 아니라, 메모리 상에 엔티티를 먼저 올려 두고 그 상태를 추적하는 역할을 합니다. 그래서 한 번 조회한 엔티티는 같은 트랜잭션 안에서 다시 조회해도 DB에 재조회하기보다, 1차 캐시에 있는 동일한 객체를 반환합니다. 이 덕분에 동일한 PK에 대해 항상 같은 인스턴스를 보장하고, 불필요한 쿼리도 줄일 수 있습니다.

또 하나 중요한 기능은 변경 감지, 즉 더티 체킹입니다. 트랜잭션이 끝나기 전, flush 시점에 영속성 컨텍스트는 엔티티의 초기 스냅샷과 현재 값을 비교해서 변경된 필드를 찾아냅니다. 그리고 개발자가 직접 update 쿼리를 작성하지 않아도, 그 변경 내용을 기반으로 적절한 UPDATE SQL을 자동으로 생성해서 DB에 반영합니다.

이 밖에도 INSERT 쿼리를 모아 두었다가 한 번에 보내는 쓰기 지연, 연관 엔티티를 필요할 때만 조회하는 지연 로딩 시 프록시 초기화의 기준 같은 부분도 영속성 컨텍스트를 기반으로 동작합니다.

정리하면, 영속성 컨텍스트는 JPA에서 엔티티를 1차 캐시에 담아 관리하고, 동일성 보장·변경 감지·쓰기 지연 같은 기능을 제공해서 트랜잭션 단위로 일관성 있는 엔티티 관리와 성능 최적화를 가능하게 해 주는 핵심 메커니즘이라고 할 수 있습니다.

Q4. 엔티티의 생명주기(Entity Lifecycle)를 설명해주세요.✅🟩🟩🟩🟩

엔티티의 생명주기는 엔티티가 생성된 순간부터 영속성 컨텍스트에서 관리되고, 분리되고, 삭제되기까지의 상태 변화를 말합니다. 보통 네 가지 상태로 나눠서 설명할 수 있습니다.

먼저 비영속(Transient) 상태입니다.
new 키워드로 객체를 만든 직후처럼, 아직 영속성 컨텍스트와 아무 연관이 없는 상태입니다. 그냥 자바 객체일 뿐이라서, 저장 메서드를 호출하지 않으면 JPA 입장에서는 존재조차 알지 못합니다.

두 번째가 영속(Persistent) 상태입니다.
엔티티 매니저의 저장 메서드를 호출하거나, find·JPQL 조회로 엔티티를 가져오면 해당 엔티티가 영속성 컨텍스트에 들어가서 관리 대상이 됩니다. 이 상태에서는 1차 캐시에 올라가 있고, 필드를 변경하면 트랜잭션 커밋 시점에 변경 감지(더티 체킹)를 통해 자동으로 UPDATE 쿼리가 나가는 등 JPA의 각종 기능이 적용됩니다.

세 번째는 준영속(Detached) 상태입니다.
원래는 영속 상태였던 엔티티가 영속성 컨텍스트와 분리된 상태입니다. detach 호출, clear, close 같은 동작 이후에 생기고, 더 이상 변경 감지나 1차 캐시 관리 대상이 아닙니다. 겉보기에는 객체가 그대로 남아 있지만, JPA는 이 엔티티를 더 이상 추적하지 않습니다.

마지막이 삭제(Removed) 상태입니다.
엔티티에 대해 remove를 호출하면 영속성 컨텍스트에서 일단 삭제 상태로 표시되고, 트랜잭션 커밋 시점에 실제로 DELETE 쿼리가 나가서 DB에서 제거됩니다.

정리하면, 엔티티는 비영속 → 영속 → 준영속 또는 삭제 흐름으로 상태가 변하고, JPA의 캐싱, 변경 감지, 삭제 반영 같은 동작이 모두 이 생명주기를 전제로 움직입니다. 그래서 상태 변화를 이해하는 것이 영속성 컨텍스트를 제대로 활용하는 데 중요하다고 할 수 있습니다.

Q5. 엔티티 매핑 어노테이션들(@Entity, @Table, @Id, @Column 등)을 설명해주세요.✅🟩🟩🟩🟩

엔티티 매핑 어노테이션은 자바 클래스와 필드를 데이터베이스 테이블과 컬럼에 연결해 주는 설정이라고 보면 됩니다. 그중에서 보통 @Entity, @Table, @Id, @Column 네 가지가 기본 축입니다.

먼저 @Entity는 “이 클래스는 JPA가 관리하는 엔티티다”라고 표시하는 어노테이션입니다. 이렇게 표시된 클래스만 영속성 컨텍스트에 올라가서 조회·저장·수정 같은 JPA 기능의 대상이 됩니다.

@Table은 이 엔티티가 어떤 테이블과 매핑될지를 더 구체적으로 지정할 때 사용합니다. 이름을 따로 주지 않으면 클래스 이름을 기반으로 테이블 이름이 결정되고, @Table을 사용하면 테이블 이름이나 스키마 이름을 명시적으로 바꿀 수 있습니다.

@Id는 엔티티의 기본 키 필드를 지정하는 어노테이션입니다. JPA는 엔티티를 식별할 때 항상 이 @Id 값을 기준으로 하기 때문에 필수이고, 여기에 @GeneratedValue 같은 어노테이션을 함께 사용해서 PK 값을 자동 생성할지, 어떤 전략을 쓸지도 정할 수 있습니다.

@Column은 필드와 컬럼 간 매핑을 세부적으로 조정할 때 사용합니다. 이름을 생략하면 필드 이름을 그대로 컬럼 이름으로 쓰지만, name 속성으로 컬럼 이름을 바꾸거나, length, nullable, unique 같은 속성으로 컬럼 제약 조건을 설정할 수 있습니다.

이 밖에도 연관 관계를 표현하는 @ManyToOne, @OneToMany 같은 어노테이션들이 따로 있고, 결국 이런 매핑 어노테이션들을 조합해서 객체 모델을 테이블 구조에 정확하게 대응시키는 것이 JPA 매핑의 핵심이라고 정리할 수 있습니다.

Q6. 기본 키(Primary Key) 생성 전략들을 비교해주세요.✅🟩🟩🟩🟩

기본 키 생성 전략은 JPA가 엔티티의 PK 값을 언제, 어디서 생성하느냐를 결정하는 방식이라고 정리할 수 있고, 대표적으로 AUTO, IDENTITY, SEQUENCE, TABLE 네 가지가 있습니다.

먼저 AUTO 전략은 JPA가 사용하는 데이터베이스에 맞춰 적절한 전략을 자동으로 선택하는 방식입니다. 개발자가 깊이 신경 쓰지 않아도 되어서 간단하지만, DB를 바꾸거나 세밀하게 튜닝하고 싶을 때는 명시적인 전략 지정이 더 선호됩니다.

IDENTITY 전략은 말 그대로 DB의 AUTO_INCREMENT 같은 컬럼에 PK 생성을 맡기는 방식입니다. insert 시점에 DB가 값을 만들어 주고, JPA는 그 값을 다시 받아옵니다. 주로 MySQL에서 많이 쓰고 설정도 간단하지만, PK가 insert 이후에야 결정되기 때문에 배치 insert 같은 최적화에 제약이 있다는 점이 단점입니다.

SEQUENCE 전략DB의 시퀀스 객체를 사용해서 PK를 미리 뽑아 쓰는 방식입니다. Oracle, PostgreSQL, H2처럼 시퀀스를 지원하는 DB에서 사용하고, insert 전에 PK를 확보할 수 있어서 배치 처리나 쓰기 지연 같은 최적화에 유리합니다. 대신 시퀀스를 지원하지 않는 DB에서는 사용할 수 없습니다.

TABLE 전략은 시퀀스를 지원하지 않는 DB에서도 쓸 수 있도록, 전용 키 관리용 테이블을 만들어서 시퀀스처럼 흉내 내는 방식입니다. DB 독립성은 높지만, 매번 그 테이블을 조회·갱신해야 해서 성능 부담과 락 이슈가 있을 수 있어 요즘 실무에서는 상대적으로 덜 쓰는 편입니다.

정리하면, IDENTITY는 단순하지만 최적화 제약이 있고, SEQUENCE는 시퀀스를 지원하는 DB에서 성능상 유리하며, TABLE은 가장 느리지만 가장 DB 독립적이고, AUTO는 이들 중에서 JPA가 알아서 골라 주는 전략이라고 정리할 수 있습니다. 실무에서는 보통 DB 특성에 맞춰 IDENTITY나 SEQUENCE를 명시해서 사용하는 경우가 많습니다.

Q7. 연관관계 매핑의 종류들을 설명해주세요. (1:1, 1:N, N:1, N:M)✅🟩🟩🟩🟩

연관관계 매핑은 엔티티 사이의 관계를 객체와 테이블에서 일관되게 표현하는 것이고, 보통 1:1, 1:N, N:1, N:M 네 가지로 나눕니다.

먼저 1:1 관계는 한 엔티티가 다른 하나와만 연결되는 경우입니다. 예를 들어 회원과 회원 상세 정보처럼 “있어도 하나, 없어도 그만”인 관계에 많이 쓰이고, 실제 테이블에서는 둘 중 한쪽에 외래 키를 두거나, 아예 PK를 공유하는 방식으로 구현할 수 있습니다.

다음으로 N:1 관계는 여러 엔티티가 하나를 가리키는 가장 흔한 형태입니다. 여러 주문이 하나의 회원을 참조하는 구조처럼, 보통 다 쪽(N)이 외래 키를 가지는 쪽이고, JPA에서도 이쪽을 연관관계의 주인으로 두는 것이 일반적입니다. 쿼리 측면에서도 가장 많이 사용되는 패턴입니다.

1:N 관계는 반대로 한 엔티티가 여러 엔티티를 컬렉션으로 들고 있는 형태입니다. 팀이 여러 회원을 리스트로 가지고 있는 경우처럼 보이지만, 실제 외래 키는 여전히 “여러 쪽” 테이블에 있어서, 순수 1:N 단방향 매핑은 쿼리가 비효율적이거나 제약이 있어서 실무에서는 주로 N:1을 주인으로 두고 양방향으로 함께 사용하는 방식이 많습니다.

마지막으로 N:M 관계는 여러 학생이 여러 강의를 듣는 것처럼 다대다 구조입니다. 데이터베이스에서는 반드시 중간 조인 테이블이 필요합니다. JPA에서 직접 N:M 매핑도 제공하지만, 중간 테이블이 숨겨져서 추가 컬럼을 넣거나 제약을 주기가 어렵기 때문에, 실무에서는 보통 중간 엔티티를 하나 두고 두 개의 N:1, 1:N 관계로 풀어서 설계하는 방식을 선호합니다.

정리하면, 연관관계 매핑은 1:1, 1:N, N:1, N:M이라는 개념적인 종류가 있고, 그중에서 N:1을 기준으로 연관관계 주인과 외래 키 위치를 어떻게 잡느냐가 설계의 핵심이라고 볼 수 있습니다.

Q8. 양방향 연관관계와 연관관계의 주인(Owner)에 대해 설명해주세요.✅🟩🟩🟩🟩

양방향 연관관계와 연관관계의 주인은 객체 입장에서의 방향과 데이터베이스 외래 키를 누가 관리하느냐를 구분하는 개념입니다. 먼저 이 둘을 분리해서 보는 게 중요합니다.

먼저 양방향 연관관계부터 말씀드리면, 객체 세계에서는 A가 B를 알고, B도 A를 아는 식으로 서로를 참조하는 두 개의 단방향 연관관계가 합쳐진 형태입니다. 예를 들어 회원이 주문 목록을 갖고 있고, 주문이 다시 회원을 참조하는 구조처럼, 코드에서는 양쪽에서 편하게 탐색할 수 있다는 장점이 있습니다. 하지만 데이터베이스 입장에서는 결국 외래 키는 한 테이블에만 존재하기 때문에, 실제 관계는 한쪽 방향으로만 관리됩니다.

여기서 나오는 개념이 연관관계의 주인(Owner)입니다. 연관관계의 주인은 실제 외래 키를 들고 있고, 그 값의 변경을 담당하는 엔티티 쪽을 말합니다. 보통 N:1 관계에서 N 쪽(@ManyToOne)이 외래 키를 가지고 있기 때문에 연관관계의 주인이 되고, 반대편 1쪽(@OneToMany)은 mappedBy로 “나는 읽기 전용 거울이다”라고 표시하는 구조가 됩니다. JPA는 항상 주인 쪽의 값만 보고 외래 키를 업데이트하기 때문에, 반대편 컬렉션만 수정하면 DB에는 반영되지 않습니다.

그래서 실무에서는 양방향 연관관계를 사용할 때
첫째, 연관관계의 주인은 외래 키를 가진 쪽으로 명확하게 정하고,
둘째, 코드에서는 편의 메서드 등을 통해 양쪽 참조를 모두 일관되게 맞춰 주는 습관이 중요합니다.

정리하면, 양방향 연관관계는 객체 탐색 편의를 위한 두 개의 단방향 관계이고, 연관관계의 주인은 외래 키를 실제로 관리하는 한쪽이며, JPA는 이 주인만 믿고 DB를 수정하기 때문에 주인 선정과 양쪽 상태 동기화가 핵심 포인트라고 볼 수 있습니다.

Q9. 지연 로딩(Lazy Loading)과 즉시 로딩(Eager Loading)의 차이점은?✅🟩🟩🟩🟩

지연 로딩과 즉시 로딩은 연관된 엔티티를 언제 DB에서 가져올지 결정하는 조회 전략입니다.

먼저 지연 로딩(Lazy Loading)“필요할 때까지 연관 엔티티를 조회하지 않고 미뤄 두는 방식”입니다.
예를 들어 주문만 먼저 가져오고, 그 주문에서 회원 정보가 정말 필요해지는 순간에 그때 추가 쿼리를 날려서 회원을 조회하는 식입니다. 초기 쿼리가 가벼워지고, 사용하지도 않을 데이터를 미리 가져오지 않으니 불필요한 조인을 줄이고 성능을 최적화하기 좋다는 장점이 있습니다. 대신 트랜잭션이 이미 끝난 시점에 연관 엔티티에 접근하면 LazyInitializationException이 날 수 있고, 컬렉션을 반복하면서 하나씩 접근할 때 N+1 문제가 발생할 수 있다는 점이 단점입니다.

반대로 즉시 로딩(Eager Loading)“엔티티를 조회하는 시점에 연관된 엔티티까지 한 번에 같이 가져오는 방식”입니다.
개발자가 따로 접근하지 않아도 알아서 조인해서 가져오니 편하고, 트랜잭션 안에서 이미 다 로딩돼 있어서 Lazy 예외가 생기지 않습니다. 다만 실제로는 안 써도 되는 연관 데이터까지 항상 끌고 와서 쿼리가 복잡해지고, 조인 과다로 인해 성능이 떨어질 수 있다는 점이 문제입니다. 연관 관계가 여러 단계로 얽혀 있으면 예측하기 어려운 쿼리가 나가기도 합니다.

정리하면, 지연 로딩은 필요한 시점까지 조회를 미뤄서 성능을 최적화하는 전략, 즉시 로딩은 편하지만 과도한 조인과 N+1 위험이 있는 전략이고, 실무에서는 보통 기본을 Lazy로 두고, 정말 필요한 화면이나 쿼리에서만 fetch join 같은 방식으로 명시적으로 데이터를 함께 조회하는 패턴을 많이 사용합니다.

Q10. N+1 문제란 무엇이며 해결 방법은?✅🟩🟩🟩🟩

N+1 문제는 한 번에 가져올 수 있는 연관 데이터를 잘못 설계해서, 쿼리가 1번이 아니라 1 + N번으로 쪼개져 나가면서 성능이 급격히 나빠지는 문제입니다.

조금 구체적으로 말하면, 예를 들어 부모 엔티티 목록을 한 번 조회하는 1개의 쿼리가 먼저 나가고, 그 후에 목록에 포함된 각 엔티티마다 연관 엔티티를 조회하는 쿼리가 N번 더 나가는 상황입니다. 겉으로는 리스트 한 번 돌았을 뿐인데, 실제 DB에는 10개면 11번, 100개면 101번 쿼리가 나가서 네트워크 왕복과 쿼리 비용이 기하급수적으로 증가하게 됩니다. JPA에서는 지연 로딩 설정이 많을 때 컬렉션을 순회하면서 연관 엔티티에 접근할 때 이런 문제가 자주 생깁니다.

해결 방법은 보통 몇 가지를 같이 사용합니다.
첫째, fetch join을 사용해서 한 번의 조인 쿼리로 필요한 연관 엔티티를 미리 같이 조회하는 방식입니다. 화면이나 API에서 어떤 연관 데이터를 반드시 함께 쓸지 명확할 때 가장 직접적인 해결책입니다.

둘째, EntityGraph나 전용 쿼리 메서드를 사용해서 필요한 연관 관계만 선택적으로 즉시 로딩하는 방법입니다. 도메인 모델은 기본을 LAZY로 두되, 특정 조회용 쿼리에서만 “이때는 같이 가져와라”라고 선언적으로 지정하는 패턴입니다.

셋째, 배치 사이즈 설정을 통해 IN 쿼리로 묶어 조회하는 최적화도 있습니다. 모든 상황을 fetch join으로 처리하기 어려울 때, Hibernate의 batch size 같은 설정으로 여러 건을 묶어서 가져오면 N+1을 완전히 없애진 못해도 상당 부분 완화할 수 있습니다.

정리하면, N+1 문제는 하나의 조회에 대해 추가로 N번의 조회가 발생하는 성능 문제이고, 실무에서는 연관관계 기본은 지연 로딩으로 두고, fetch join·EntityGraph·배치 사이즈 등을 조합해서 필요한 시점에만 효율적으로 연관 데이터를 가져오도록 설계하는 식으로 해결합니다.

Q11. 영속성 전이(Cascade)와 고아 객체(Orphan Removal)를 설명해주세요.✅🟩🟩🟩🟩

영속성 전이와 고아 객체는 부모–자식처럼 강하게 묶인 연관관계에서 엔티티의 생명주기를 함께 관리하기 위한 기능입니다. 둘 다 연관 엔티티를 자동으로 처리해 준다는 공통점이 있지만, 초점이 조금 다릅니다.

먼저 영속성 전이(Cascade)
부모 엔티티에 대한 저장, 삭제 같은 작업을 연관된 자식 엔티티에도 전파하는 기능입니다. 예를 들어 하나의 주문이 여러 주문상품을 가지고 있을 때, 주문 하나만 저장해도 주문상품들이 같이 저장되거나, 주문을 삭제하면 주문상품도 같이 삭제되도록 만드는 개념입니다. PERSIST, REMOVE, ALL 같은 옵션에 따라 어떤 연산을 전파할지 결정하고, 보통 완전히 부모에 종속적인 자식 엔티티일 때 사용하는 것이 안전합니다. 반대로 여러 Aggregate에서 공유되는 엔티티에 무분별하게 Cascade를 걸면 의도치 않은 삭제나 저장이 일어날 수 있어서 주의가 필요합니다.

반면에 고아 객체 제거(Orphan Removal)
이미 영속 상태에 있는 관계에서, 부모와의 연관관계에서 떨어져 “고아가 된 자식”을 자동으로 삭제해 주는 기능입니다. 예를 들어 부모 컬렉션에서 자식 엔티티를 빼 버리거나, 부모 쪽 연관 필드를 null로 만들어서 더 이상 어떤 부모에도 속하지 않게 되면, JPA가 그 자식을 고아로 판단하고 DELETE 쿼리를 날립니다. 마치 “이 자식은 이 부모에게만 속하고, 부모가 더 이상 돌보지 않으면 함께 제거해야 한다”는 강한 소유 관계, 컴포지션에 가까운 모델에 잘 어울립니다.

정리하면, 영속성 전이는 부모의 연산을 자식에게 ‘전파’하는 기능, 고아 객체 제거는 부모와의 관계가 끊어진 자식을 자동으로 ‘삭제’하는 기능이고, 둘 다 “이 연관관계는 생명주기를 함께 가져가야 한다”는 강한 소유 개념이 있을 때 신중하게 적용하는 것이 핵심입니다.

Q12. JPQL이란 무엇이며 네이티브 쿼리와의 차이점은?🟩🟩🟩🟩🟩

JPQL은 JPA에서 엔티티를 대상으로 질의하는 객체 지향 쿼리 언어이고, 네이티브 쿼리는 데이터베이스에 직접 날리는 순수 SQL이라고 정리할 수 있습니다.

우선 JPQL(Java Persistence Query Language)은 테이블과 컬럼이 아니라 엔티티와 엔티티의 필드를 기준으로 작성하는 쿼리입니다.
그래서 “테이블 이름, 컬럼 이름” 대신 엔티티 이름, 필드 이름을 쓰고, JPA 구현체가 이를 각 DB에 맞는 SQL로 변환해 줍니다. 이 덕분에 데이터베이스 독립성이 높고, 엔티티 필드명을 바꾸거나 리팩터링해도 IDE 도움을 받아 비교적 안전하게 수정할 수 있습니다. 또 fetch join 같은 JPA 전용 기능을 활용해서 연관 엔티티 조회도 객체 관점에서 다루기 좋습니다.

반면 네이티브 쿼리(Native Query)는 말 그대로 DB에 의존적인 순수 SQL을 그대로 사용하는 방식입니다.
DB가 제공하는 특정 함수, 힌트, 윈도우 함수, 복잡한 통계 쿼리 같은 것을 그대로 활용할 수 있어서, JPQL로 표현하기 어려운 복잡한 쿼리나 성능 튜닝이 필요한 부분에 유리합니다. 하지만 DB 벤더에 종속적이고, 테이블·컬럼 이름을 직접 쓰기 때문에 스키마가 바뀌면 쿼리를 일일이 손봐야 하고, 코드에서 타입 안정성도 떨어지는 편입니다.

정리하면, JPQL은 엔티티 중심의 객체 지향 쿼리로 유지보수성과 DB 독립성이 강점이고, 네이티브 쿼리는 SQL을 그대로 써서 DB 고유 기능과 복잡한 쿼리를 다룰 때 강점이 있습니다. 실무에서는 기본적으로 JPQL·Spring Data JPA를 사용하고, 필요할 때 제한된 범위에서 네이티브 쿼리를 보완적으로 사용하는 패턴이 많습니다.

Q13. Fetch Join과 일반 Join의 차이점을 설명해주세요.🟩🟩🟩🟩🟩

Fetch Join은 연관 엔티티를 한 번에 함께 로딩하는 JPA 전용 조인이고, 일반 Join은 단순히 조인 조건만 걸고 로딩 방식에는 영향을 주지 않는 조인입니다.

먼저 Fetch Join은 “이 연관 엔티티까지 같이 가져와서 영속성 컨텍스트에 채워 넣어라”라는 의미를 갖습니다. 한 번의 쿼리로 부모와 자식을 모두 가져와서, 컬렉션이나 연관 필드에 실제 엔티티들이 채워진 상태가 됩니다. 그래서 지연 로딩으로 설정돼 있더라도 그 쿼리를 날리는 시점에 미리 다 가져오기 때문에 N+1 문제를 해결하거나, 화면에서 꼭 같이 써야 하는 연관 데이터들을 한 번에 로딩할 때 주로 사용합니다.

반대로 일반 Join은 말 그대로 “조인해서 조건을 거르거나 검색 범위를 좁히는 용도”일 뿐입니다. JPQL에서 일반 조인을 사용해도, select 대상이 되는 것은 여전히 루트 엔티티고, 연관 필드는 여전히 지연 로딩 프록시로 남아 있을 수 있습니다. 그래서 나중에 연관 엔티티에 접근하면 또 쿼리가 추가로 나가고, 잘못 사용하면 여전히 N+1 문제가 발생할 수 있습니다.

정리하면, 두 경우 모두 SQL 레벨에서는 조인이 나가지만, Fetch Join은 “연관 엔티티까지 한 번에 로딩해서 그래프를 채운다”는 로딩 전략에 대한 힌트이고, 일반 Join은 “조회 조건을 위한 조인일 뿐, 실제 로딩 시점에는 관여하지 않는다”는 차이가 있습니다. 그래서 실무에서는 기본은 지연 로딩으로 두고, 정말 함께 필요한 경우에만 Fetch Join으로 명시적으로 가져오는 패턴을 많이 사용합니다.

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

변경 감지(Dirty Checking)는 영속성 컨텍스트가 엔티티의 변경 사항을 자동으로 찾아서, 개발자가 update 쿼리를 직접 호출하지 않아도 DB에 반영해 주는 기능입니다.

조금 더 구체적으로 말하면, 엔티티가 영속 상태가 되는 순간 JPA가 그 시점의 값을 스냅샷으로 하나 가지고 있고, 이후에 같은 엔티티의 필드를 수정하면 트랜잭션을 커밋하거나 flush가 일어나는 시점에 현재 값과 초기 스냅샷을 비교합니다. 이때 달라진 필드가 있으면 해당 엔티티를 변경된 것으로 판단하고, 그 차이를 기반으로 UPDATE SQL을 자동으로 생성해서 DB에 반영합니다.

이 메커니즘 덕분에 서비스 코드에서는 보통 “엔티티를 조회 → 필드 값만 수정”하는 형태로 로직을 작성하고, 별도의 저장 메서드나 update 쿼리를 호출하지 않아도 됩니다. 즉, 변경 감지는 엔티티 수정 로직을 객체 지향스럽게 유지하면서도, SQL 작성과 중복된 갱신 코드를 줄여 주는 핵심 기능이라고 볼 수 있습니다.

정리하면, 변경 감지는 영속성 컨텍스트가 관리하는 엔티티의 이전 상태와 현재 상태를 비교해서, 변경된 부분만 자동으로 DB에 반영해 주는 JPA의 자동 업데이트 메커니즘이고, 트랜잭션 범위 안에서 엔티티만 수정하면 된다는 점이 가장 큰 특징입니다.

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

@Transactional은 스프링이 트랜잭션의 시작과 종료를 관리해 주는 어노테이션이고, JPA는 이 트랜잭션 범위 안에서 영속성 컨텍스트를 활용해 변경 감지·지연 로딩 등을 수행하는 구조입니다. 둘은 따로 동작하는 것이 아니라, 보통 “하나의 서비스 메서드 = 하나의 트랜잭션 = 하나의 영속성 컨텍스트”라는 단위로 함께 묶여서 동작합니다.

구체적으로는 @Transactional이 붙은 메서드가 호출되면, 스프링이 먼저 데이터베이스 트랜잭션을 시작하고 그에 연결된 EntityManager와 영속성 컨텍스트를 생성·할당합니다. 그 다음 비즈니스 로직이 실행되는 동안 조회된 엔티티들이 이 영속성 컨텍스트에서 관리되고, 메서드가 정상 종료되면 JPA가 변경 감지(Dirty Checking)를 통해 수정된 엔티티를 flush로 DB에 반영한 뒤, 마지막에 트랜잭션 커밋이 일어납니다. 예외가 발생하면 커밋 대신 롤백이 수행됩니다.

또한 @Transactional은 JPA의 특징과 맞물려 부가적인 효과도 있습니다. 예를 들어 트랜잭션 범위 안에서만 지연 로딩이 안전하게 동작하고, readOnly 옵션을 사용하면 플러시를 최소화해 불필요한 변경 감지 작업을 줄일 수 있습니다. 반대로 트랜잭션이 없는 상태에서 엔티티를 조회하고 나중에 연관 관계에 접근하면 LazyInitializationException이 발생하는 것도, 영속성 컨텍스트가 트랜잭션과 함께 열리고 닫히기 때문입니다.

정리하면, @Transactional은 트랜잭션 경계를 정의하고 JPA 영속성 컨텍스트의 생명주기를 관리하는 스프링의 도구이고, JPA의 변경 감지·지연 로딩·쓰기 지연 같은 기능들은 이 트랜잭션 경계 안에서 제대로 동작한다고 볼 수 있습니다. 그래서 서비스 계층에서 적절하게 @Transactional을 선언하는 것이 JPA를 올바르게 사용하는 핵심이라고 할 수 있습니다.

Q16. 낙관적 락(Optimistic Lock)과 비관적 락(Pessimistic Lock)의 차이점은?🟩🟩🟩🟩🟩

낙관적 락과 비관적 락은 동시에 같은 데이터를 수정할 수 있는 상황에서, 충돌을 ‘언제 어떻게 막을 것인가’에 대한 전략의 차이입니다.

낙관적 락(Optimistic Lock)은 말 그대로 “충돌이 자주 일어나지 않을 것”이라고 보고, 먼저 자유롭게 읽고 작업을 진행한 뒤, 마지막에 변경 충돌이 났는지 검사해서 문제가 있으면 그때 예외를 내거나 다시 시도하는 방식입니다. 예를 들어 버전 컬럼을 두고, 조회할 때의 버전과 업데이트 시점의 버전을 비교해서 누군가 먼저 수정했다면 업데이트를 막는 식으로 동작합니다. 장점은 별도의 DB 락을 오래 잡지 않기 때문에 동시에 많은 요청이 들어와도 병렬성이 높고 성능 부담이 상대적으로 적다는 점입니다. 대신 충돌이 자주 나는 구간에서는 예외가 많이 발생하고, 재시도 로직을 따로 설계해야 하는 부담이 있습니다.

비관적 락(Pessimistic Lock)은 반대로 “충돌이 일어날 가능성이 크다”라고 보고, 데이터를 읽거나 수정할 때 아예 DB에서 락을 걸어 버려서 다른 트랜잭션이 손대지 못하게 막는 방식입니다. 주로 SELECT … FOR UPDATE 같은 행 단위 잠금을 사용하고, 한 트랜잭션이 끝날 때까지 다른 트랜잭션은 대기하거나 바로 예외를 받게 됩니다. 이 방식의 장점은 충돌이 발생해도 애플리케이션 레벨에서 별도로 검증하지 않아도 안정적으로 순서를 보장할 수 있다는 점이지만, 락 경쟁이 생기면 대기 시간이 늘어나고 데드락 같은 문제도 신경 써야 합니다.

정리하면, 낙관적 락은 락을 잡지 않고 나중에 충돌을 검출해서 조정하는 전략이고, 비관적 락은 아예 먼저 락을 걸어서 다른 변경을 막는 전략입니다. 일반적으로는 충돌이 드문 도메인에서는 낙관적 락으로 성능과 병렬성을 높이고, 돈·재고처럼 동시 수정에 민감하고 충돌 비용이 매우 큰 데이터에는 비관적 락을 신중하게 사용하는 식으로 선택하는 편입니다.

💡 추가 예상 질문 펼치기
Q17. 1차 캐시의 동작 방식과 장점을 설명해주세요.

Q18. EntityManager의 주요 메서드들(persist, find, merge, remove)을 설명해주세요.

Q19. @Embedded와 @Embeddable은 언제 사용하나요?

Q20. JPA Auditing 기능(@CreatedDate, @LastModifiedDate)에 대해 설명해주세요.

Q21. @OneToMany 단방향과 양방향의 차이점과 권장 방식은?

Q22. 준영속(Detached) 상태란 무엇이며 다시 영속 상태로 만드는 방법은?

Q23. JPA에서 페이징 처리는 어떻게 하나요?

Q24. Spring Data JPA의 쿼리 메서드 네이밍 규칙을 설명해주세요.

Q25. @Query 어노테이션은 언제 사용하나요?

Q26. DTO로 직접 조회하는 방법과 장점은 무엇인가요?

Q27. JPA 사용 시 성능 최적화를 위해 주의해야 할 점은?

Q28. 엔티티 설계 시 주의사항은 무엇인가요?

Q29. JPA에서 Bulk 연산의 특징과 주의사항은?

Q30. flush()는 언제 호출되며 어떤 역할을 하나요?