Spring/스프링가링가링

JPA 지연로딩 사용시 N+1 문제

한 면만 쓴 종이 2022. 10. 6. 15:07

< 주제 >

JPA 지연로딩 사용시 N+ 1 문제

ORM - JPA, Spring Data JPA

Embedded Database를 활용한 JPA test

 

 

 

JPA 모든 N+1 발생 케이스과 해결책 (velog.io)

 

JPA 모든 N+1 발생 케이스과 해결책

N+1이 발생하는 모든 케이스 (즉시로딩, 지연로딩)에서의 해결책과 그 해결책에서의 문제를 해결하는 방법에 대해 이야기 하려합니다 😀

velog.io

 

 

JPA N + 1 문제 발생 이유

객체에 대해서 조회한다고 해도 다양한 연관관계들이 매핑에 의해서 관계가 맺어진 다른 객체가 함께 조회되는 경우에 N+1이 발생

 

=> 지연로딩 사용 (연관된 객체를 사용하는 시점에 로딩)

데이터를 처음 조회한 시점에서는 N + 1 문제가 발생하지 않음

하지만, 하위 엔티티를 사용하게 되면 추가 조회가 이때 발생하게 되면서 N + 1 문제가 발생함

 

 

[JPA] N+1 문제 원인 및 해결방법 알아보기 — 슬기로운 개발생활 (tistory.com)

 

[JPA] N+1 문제 원인 및 해결방법 알아보기

JPA를 사용하면 자주 만나게 되는 것이 N + 1 문제이다. N + 1 문제는 성능에 큰 영향을 줄 수 있기 때문에 N + 1 문제가 무엇이고 어떤 상황에 발생되는지, 어떻게 해결하면 되는지에 대해 알아보고

dev-coco.tistory.com

 

해결 방법

 

 

 

1. Fetch Join

미리 두 테이블을 JOIN 해서 한 번에 모든 데이터를 탐색하면 N+1의 문제가 발생하지 않을 것이라는 생각에서 나온 방법

=> 연관된 두 테이블을 JOIN하는 쿼리를 직접 작성하는 것

 

내부 조인 (inner join)을 함

 

@Query("select DISTINCT o from Owner o join fetch o.pets")
List<Owner> findAllJoinFetch();
 

Fetch Join의 단점

모든 데이터를 한 번에 가져오기 때문에 JPA가 제공하는 Pageable를 사용하지 못 함

1:N의 관계가 두 개 이상인 경우 사용 불가

패치 조인 대상에게 별칭 부여 불가능

 

 

2. @EntityGraph

 

attributePaths에 같이 조회할 연관된 엔티티명을 적으면 됨 (여러개도 가능)

1번 Fetch Join과 마찬가지로 Query문 작성 필요

 

외부조인(outer join)을 함

 

@EntityGraph(attributePaths = {"pets"})
@Query("select DISTINCT o from Owner o")
List<Owner> findAllEntityGraph();

 

 
위의 두 방법은 모두 카테시안 곱이 발생하여 중복이 생길 수 있다.

*카테시안 곱

Form절에 2개 이상의 Table이 있을 때, 두 Table 사이에 유효 join 조건을 적지 않았을 때 해당 테이블에 대한 모든 데이터를 전부 결합하여 Table에 존재하는 행 개수를 곱한 만큼의 결과값이 반환되는 것

[SQL] 카테시안 곱(Cartesian Product)이란? (tistory.com)

 

해결 방법

 

1. JPQL에 DISTINCT 를 추가하여 중복 제거

@Query("select DISTINCT o from Owner o join fetch o.pets")
List<Owner> findAllJoinFetch();

@EntityGraph(attributePaths = {"pets"})
@Query("select DISTINCT o from Owner o")
List<Owner> findAllEntityGraph();
 
2. OneToMany 필드 타입을 Set으로 선언하여 중복 제거
@OneToMany(mappedBy = "owner", fetch = FetchType.EAGER)
private Set<Pet> pets = new LinkedHashSet<>();​

=> 순서 보장이 필요한 경우는 LinkedHashSet 사용