새로운 내용을 공부할 때
새로운 내용의 공부를 시작할 때 용어의 정의를 이해하지 못하거나 정확하게 알지 못한다면 그 용어가 포함된 문장을 이해하지 못합니다.
작은 단어 하나가 내용을 이해하지 못하게 하기 때문에 용어를 정확하게 이해하는 것이 중요합니다.

1 분 소요

📌 2025-03-14 JPA N+1

1. 오늘의 학습 주제

  • N+1 이 발생하는 이유

2. N+1

근본적인 원인

  • 영속성 컨택스트의 동일성 보장 원칙

    하이버네이트는 동일한 엔티티 ID에 대해 하나의 인스턴스만 존재하도록 보장합니다.

  • 지연 로딩으로 인한 프록시 객체의 개별 순회

    JPA는 지연로딩으로 설정하면 연관관계 엔티티를 ID만 가지며, 뒤늦은 로딩이 가능한 프록시 객체를 생성하여 대입합니다.

발생되는 상황

영속성 컨텍스트의 동일성 보장 원칙이 동작하는 경우

1:N 관계에서 즉시로딩으로 설정하는 경우에 발생합니다.

학생과 담당 교사는 N:1 관계입니다.

결국 JPA도 테이블 결과를 읽고나서 오브젝트를 생성하고 초기화하는 방식입니다.

1:N을 조인으로 한꺼번에 읽어오면 선생님은 한명인데 오브젝트는 3개가 만들어져야하는 상황입니다.

  1. 데이터 중복을 방지하고 메모리 효율성을 위해
  2. 영속성 컨텍스트의 동일성 보장으로

하이버네이트는 내부 구현 방식이 즉시로딩 && 1:N 상황이라면 반대로 조회를 합니다.

내부에서 담당 교사를 조회하고 담당 교사와 관련된 학생을 다시 조회합니다. 이 과정에서 N+1 문제가 발생합니다.

실제 쿼리가 where id = xx으로 N번 실행이 됩니다.

지연 로딩으로 인한 프록시 객체의 순환

if(지연 로딩 관계로 설정 && 연관관계 엔티티를 조회나 사용)하는 경우에 발생합니다.

지연 로딩으로 프록시 객체를 사용하는데 이때 순회를 하거나 조회를 하거나 엔티티가 응답으로 그대로 반환되는 경우에 뒤늦은 초기화를 해야하기에 N+1 문제가 발생합니다.

문제 해결 방안

엔티티 기능이 필요한 경우

  1. 배치 패칭

    hibernate.default_batch_fetch_size=10

  2. 엔티티 그래프 사용

    @EntityGraph(attributePaths = {"students"})
    List<Teacher> findByIdIn(List<Long> ids);
    
  3. 서브셀렉트 페칭

    @OneToMany(mappedBy = "teacher", fetch = FetchType.LAZY)
    @Fetch(FetchMode.SUBSELECT)
    private List<Student> students;
    
  4. join fetch 사용 (jpql)

배치 배칭은 위에 where id in (...)으로 실행하는 방식

서브셀렉트 페칭은 하이버네이트 고유기술이다.

select * from student s where s.teacher_id in (
    select t.id from teacher t where t.some_condition = ?
)

엔티티 기능이 필요없는 경우

  1. DTO 프로젝션 사용
  2. 원시 SQL, JPQL 조회
  3. 조회 전용 레포지토리 설계

댓글남기기