새로운 내용을 공부할 때
새로운 내용의 공부를 시작할 때 용어의 정의를 이해하지 못하거나 정확하게 알지 못한다면 그 용어가 포함된 문장을 이해하지 못합니다.
작은 단어 하나가 내용을 이해하지 못하게 하기 때문에 용어를 정확하게 이해하는 것이 중요합니다.
TIL) JPA N+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:N
상황이라면 반대로 조회를 합니다.
내부에서 담당 교사를 조회하고 담당 교사와 관련된 학생을 다시 조회합니다. 이 과정에서 N+1 문제가 발생합니다.
실제 쿼리가 where id = xx
으로 N
번 실행이 됩니다.
지연 로딩으로 인한 프록시 객체의 순환
if(지연 로딩 관계로 설정 && 연관관계 엔티티를 조회나 사용)
하는 경우에 발생합니다.
지연 로딩으로 프록시 객체를 사용하는데 이때 순회를 하거나 조회를 하거나 엔티티가 응답으로 그대로 반환되는 경우에 뒤늦은 초기화를 해야하기에 N+1 문제가 발생합니다.
문제 해결 방안
엔티티 기능이 필요한 경우
-
배치 패칭
hibernate.default_batch_fetch_size=10
-
엔티티 그래프 사용
@EntityGraph(attributePaths = {"students"}) List<Teacher> findByIdIn(List<Long> ids);
-
서브셀렉트 페칭
@OneToMany(mappedBy = "teacher", fetch = FetchType.LAZY) @Fetch(FetchMode.SUBSELECT) private List<Student> students;
-
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 = ?
)
엔티티 기능이 필요없는 경우
- DTO 프로젝션 사용
- 원시 SQL, JPQL 조회
- 조회 전용 레포지토리 설계
댓글남기기