N+1 문제란 ?
요청이 1개의 쿼리로 처리 되길 기대했지만, N개의 추가 쿼리(하위 엔티티들 조회 쿼리)가 발생하는 현상
예시
JPA에서 "팀(1개) - 팀원(5명)" 과 같은 1대 N의 연관관계가 Lazy Fectch로 존재할 경우, 팀의 팀원을 조회하면 총 6번의 쿼리가 실행된다. 먼저, 처음 팀을 조회하는 쿼리가 발생하고, 해당 팀의 팀원들에 대한 쿼리가 5개 실행되어 총 6번의 쿼리분이 발생하기 때문이다.
원인
JPA에서 FetchType을 Lazy Fetch Loading으로 설정할 경우, 처음 팀을 조회 시 팀원 데이터를 함께 가져오지 않고, 팀원에 대한 Proxy 객체만 가져온다. 이후, 조회한 팀에서 팀원을 조회할 때, JPA의 1차 캐시에서 팀원에 대한 데이터가 있는지 확인 후 쿼리를 실행하여 각각의 팀원 객체에 대한 데이터를 가져온다.
요약하면, Lazy Fetch 때문에, 팀을 조회할 때, 팀원 데이터가 누락되어 각각의 팀원에 대한 쿼리를 새로 실행하는 것이다!
🔥 Eager Loding 으로 바꿔도 N+1 문제는 발생한다.
JPA는 조회 대상을 기준으로 쿼리문을 날리기 때문에 Eager Loding으로 바꿔도, 처음에 팀을 조회 후, 곧 바로 팀원에 대한 N개의 쿼리가 실행된다.
해결 방법
- join fetch
- Inner Join
- 하위 엔티티의 수만큼 중복 발생 (해당 1:다 필드에서 List가 아닌 Set)
@Query("select e from 엔티티 e join fetch e.하위엔티티")
List findAll();
- @EntityGraph
- Outer Join
- 하위 엔티티의 수만큼 중복 발생
@EntityGraph(attributePaths = "하위 엔티티")
@Query("select e from 엔티티 e"0
List findAll();
위의 join fetch와 entityGrapgh를 이용할 경우, 하위 엔티티의 수만큼 중복이 발생한다.
해당 중복을 제거하는 방법은 아래와 같이 2가지가 있다.
- Set 자료형
- 1:다 필드에서 List형이 아닌, Set 자료형을 사용해 중복을 제거.
- distinct
- join fetch 쿼리 애너테이션을 추가할때, "select Distinct ... " 와 같이 DISTINCT를 추가한다.
참고 출처
'JPA' 카테고리의 다른 글
JPA - 연관관계 주인(feat. mappedBy) (0) | 2023.05.19 |
---|---|
영속성 컨텍스트! (2) | 2023.05.11 |
자바의 트랜잭션 (0) | 2023.05.09 |
엔티티 매니저와 영속성 컨텍스트 정리 (0) | 2023.05.01 |
1. JPA의 등장 (0) | 2023.04.26 |