CS 스터디) JPA와 영속성 컨텍스트

동동주·2025년 7월 26일
0

🧬 JPA에서 N+1 문제란 무엇인가요?


연관관계가 있는 엔티티를 조회할 때 조회된 개수 N개 만큼의 쿼리가 추가로 발생하는 것

🧬 왜 JPA는 Join 대신 추가 쿼리를 발생하게 했나요?

많은 데이터를 가져오는데 즉시 사용하지 않는 경우에 Join 쿼리 사용 시 비효율적인 상황이 발생하고, 이러한 상황을 방지하기 위해 데이터를 사용하는 시점에 불러와야 하는 구현(LAZY 로딩)을 추가해야했다. 그래서 LAZY 로딩을 추가하고 기본적으로는 Join 대신 추가 조회 쿼리를 사용하는 방향으로 설계했다.

🧬 EAGER와 LAZY 로딩의 차이를 설명해주세요.

즉시로딩이란, 데이터를 조회할 때 연관된 모든 객체의 데이터까지 한 번에 불러오는 것이다.
지연로딩이란, 필요한 시점에 연관된 객체의 데이터를 불러오는 것이다.

🧬 JPA의 FetchType.LAZY를 설정했는데도 N+1 문제가 발생할 수 있는 경우는 언제인가요?

When 언제 발생하는가?
JPA Repository를 활용해 인터페이스 메소드를 호출할 때(Read 시)
How 어떤 상황에 발생되는가?
JPA Fetch 전략이 EAGER 전략으로 데이터를 조회하는 경우
JPA Fetch 전략이 LAZY 전략으로 데이터를 가져온 이후 연관 관계인 하위 엔티티를 다시 조회하는 경우

🧬 DTO로 직접 조회(QueryDSL 또는 JPQL)을 사용해 N+1을 해결하는 방식의 장단점을 설명해주세요.

✅ 장점

1. N+1 문제 해결
연관된 엔티티를 LAZY로 설정했더라도, DTO로 필요한 데이터만 조인해서 한 번에 가져오기 때문에 N+1 문제가 발생하지 않습니다.

2. 성능 최적화
엔티티 전체를 불러오는 것이 아니라 필요한 필드만 조회하므로 쿼리 성능과 네트워크 I/O가 향상됩니다.

불필요한 연관 관계 로딩이 없어 메모리 사용도 줄어듭니다.

3. 쿼리 명확성
어떤 데이터를 어떻게 가져오는지 JPQL 또는 QueryDSL로 명확하게 표현할 수 있어, 쿼리 구조와 동작을 개발자가 완벽히 통제할 수 있습니다.

4. API 응답 형태에 맞춤
클라이언트에 반환할 형태로 직접 DTO를 구성하므로, 컨트롤러나 서비스에서 추가 가공이 불필요합니다.

❌ 단점

1. 재사용성 낮음
엔티티가 아닌 DTO에 맞춘 쿼리이기 때문에, 다른 화면이나 목적에 따라 쿼리를 새로 작성해야 할 가능성이 높습니다.

2. 타입 안정성이 떨어질 수 있음 (JPQL)
JPQL에서 new 키워드를 사용하는 방식은 컴파일 타임에 오류를 잡기 어렵고, 런타임 오류 가능성이 있습니다.

QueryDSL은 이 문제를 어느 정도 보완하지만, 여전히 DTO 생성자나 필드명 변경 시 오류 발생 가능성이 있습니다.

3. 엔티티의 기능을 사용할 수 없음
엔티티를 직접 가져오지 않기 때문에, JPA의 변경 감지(dirty checking), 지연 로딩, 연관관계 편의 메서드 등을 활용할 수 없습니다.

데이터를 수정하려면 다시 엔티티를 별도로 조회해야 합니다.

4. 쿼리 복잡도 증가
복잡한 화면일수록 조회 쿼리가 길고 복잡해지며, 유지보수가 어려워질 수 있습니다.

특히 여러 테이블을 조인하여 DTO를 구성하는 경우, 실수로 인해 잘못된 결과를 가져오거나 성능 저하를 유발할 수 있습니다.

🧬 컬렉션 Fetch Join의 한계점은 무엇이며, 해결 방법은 무엇인가요?

❗한계점

  1. 중복 데이터 발생
    컬렉션을 Fetch Join 하면 조인된 수만큼 부모 엔티티가 중복되어 조회됩니다.
    예: 1:N 관계에서 1에 해당하는 엔티티가 N개 만큼 중복됨

  2. 페이징 불가능
    컬렉션 Fetch Join을 사용할 경우 JPA는 메모리에서 페이징을 시도하기 때문에 실제 DB 쿼리에 LIMIT, OFFSET이 적용되지 않음
    결과적으로 데이터가 많을 경우 성능 저하 또는 OutOfMemory 오류 발생 가능

3.하나 이상의 컬렉션 Fetch Join 불가
JPA에서는 컬렉션에 대한 Fetch Join을 두 개 이상 사용할 수 없음

오류 발생: query specified join fetching, but the owner of the fetched association was not present in the select list

✅ 해결 방법

  1. @BatchSize 또는 글로벌 설정 사용
    Hibernate 설정 또는 엔티티에서 @BatchSize(size = n) 사용
    IN 쿼리로 한 번에 여러 건 로딩, N+1 문제 해결에 효과적

  2. DTO로 직접 조회
    필요한 데이터만 DTO로 직접 조회 (QueryDSL 또는 JPQL 사용)
    페이징도 자유롭게 가능하고, 컬렉션 중복 문제도 없음

🧬 ORM의 정의와 장점에 대해서 설명해주세요.

🔹 정의
ORM(Object-Relational Mapping)은 객체지향 프로그래밍 언어의 객체와 관계형 데이터베이스의 테이블 간의 불일치를 자동으로 매핑해주는 기술입니다.
대표적으로 JPA, Hibernate 등이 있습니다.

✅ 장점

  1. 생산성 향상
    SQL 작성 없이 객체 조작만으로 CRUD 가능

  2. 객체 지향적 개발
    테이블이 아닌 객체 중심의 설계 및 개발 가능

  3. 유지보수 용이
    코드 기반이므로 리팩터링 및 추적이 쉬움

  4. DB 종속성 감소
    Dialect 설정만 변경하면 DB 변경 용이

  5. 캐시 및 성능 최적화
    1차 캐시, 지연 로딩, 배치 처리 등의 기능 제공

🧬 영속성 컨텍스트란 무엇인지 설명해주세요.

영속성 컨텍스트(Persistence Context) 는 엔티티를 영속 상태로 관리하는 JPA의 논리적인 작업 공간입니다.
EntityManager가 관리하며, 영속성 컨텍스트 안에 있는 엔티티는 다음과 같은 특징을 가집니다:

  1. 1차 캐시
    같은 엔티티를 여러 번 조회하면 DB에서 다시 가져오지 않고 캐시된 것을 반환

  2. 변경 감지(Dirty Checking)
    엔티티 값 변경 시 트랜잭션 커밋 시점에 자동으로 UPDATE 쿼리 생성

  3. 쓰기 지연 SQL 저장소
    트랜잭션 커밋 시점에 SQL을 한 번에 보냄

  4. 동일성 보장
    같은 PK를 가진 엔티티는 동일한 객체로 반환 (== 비교 가능)

🧬 영속성 텍스트는 트랜잭션 단위로 동작하는데 이와 관련해 Entity Manger와 연관지어 설명해주세요.

영속성 컨텍스트는 트랜잭션 단위로 생성되고 종료되는 것이 일반적입니다. 이와 관련된 EntityManager의 역할은 다음과 같습니다:

EntityManager는 영속성 컨텍스트를 관리하는 핵심 인터페이스입니다.

트랜잭션이 시작되면 EntityManager가 영속성 컨텍스트를 생성하고,

트랜잭션이 커밋되거나 롤백되면 해당 컨텍스트는 종료되며 더 이상 엔티티를 관리하지 않습니다.


@Transactional
public void updateUserName(Long id, String name) {
    User user = entityManager.find(User.class, id); // 영속 상태
    user.setName(name); // 변경 감지
    // 트랜잭션 커밋 시점에 update 쿼리 자동 발생
}

즉, 트랜잭션 안에서 EntityManager는 같은 영속성 컨텍스트를 유지하며, 트랜잭션이 끝나면 컨텍스트도 함께 종료됩니다. 이 구조가 JPA의 핵심 동작 기반입니다.


출처:
https://ksh-coding.tistory.com/146#2.%20%EA%B7%B8%EB%A0%87%EB%8B%A4%EB%A9%B4%20JPA%EB%8A%94%20%EC%99%9C%20%EA%B7%B8%EB%A0%87%EA%B2%8C%20%EC%84%A4%EA%B3%84%ED%96%88%EC%9D%84%EA%B9%8C%3F%20(N%2B1%20%EB%AC%B8%EC%A0%9C%20%EA%B7%BC%EB%B3%B8%EC%A0%81%EC%9D%B8%20%EC%9B%90%EC%9D%B8%20%EC%B6%94%EC%B8%A1)-1

0개의 댓글