본문 바로가기

프로그래밍/JPA

N+1 (블로그글 요약함)

https://jojoldu.tistory.com/165

 

JPA N+1 문제 및 해결방안

안녕하세요? 이번 시간엔 JPA의 N+1 문제에 대해 이야기 해보려고 합니다. 모든 코드는 Github에 있기 때문에 함께 보시면 더 이해하기 쉬우실 것 같습니다. (공부한 내용을 정리하는 Github와 세미나+

jojoldu.tistory.com

하위 엔티티들을 첫 쿼리 실행시 한번에 가져오지 않고, Lazy Loading으로 필요한 곳에서 사용되어 쿼리가 실행될때 발생하는 문제가 N+1 쿼리 문제

 

  • join fetch를 사용하는 것

@Query("select a from Academy a join fetch a.subjects")

이 방법은 불필요한 쿼리문이 추가되는 단점이 있습니다.

 

 

  • 2번째 방법은 @EntityGraph을 사용하는 것

@EntityGraph(attributePaths = "subjects")

 

 

JoinFetch는 Inner Join, Entity Graph는 Outer Join이라는 차이점이 있음

공통적으로 카테시안 곱(Cartesian Product)이 발생하여 Subject의 수만큼 Academy가 중복 발생

  • 필드의 타입을 Set으로 선언하는 것(LinkedHashSet을 사용하여 순서를 보장)
  • 2번째 방법은 distinct를 사용하여 중복을 제거하는 것

 

 

 

https://jojoldu.tistory.com/457

 

MultipleBagFetchException 발생시 해결 방법

JPA의 N+1 문제에 대한 해결책으로 Fetch Join을 사용하다보면 자주 만나는 문제가 있습니다. 바로 MultipleBagFetchException 입니다. 이 문제는 2개 이상의 OneToMany 자식 테이블에 Fetch Join을 선언했을때 발..

jojoldu.tistory.com

MultipleBagFetchException 
이 문제는 2개 이상의 OneToMany 자식 테이블에 Fetch Join을 선언했을때 발생합니다.

@Query("SELECT s " +
            "FROM Store s " +
            "JOIN FETCH s.products " +
            "JOIN FETCH s.employees")
    List<Store> findAllByFetchJoin ();

 

해결책은 하이버네이트의 default_batch_fetch_size 옵션

(1개씩 사용되는 조건문을 in절로 묶어서 조회)

 

테스트 해보자
@TestPropertySource(properties = "spring.jpa.properties.hibernate.default_batch_fetch_size=1000") // 옵션 적용

 

만약 1만개의 Store (부모 엔티티)가 조회된다면

옵션 적용시 (1000개)

Store 조회 쿼리 1번

각 Store의 Product 조회 쿼리가 10번 (10,000 / 1,000)

각 Store의 Employee 조회 쿼리가 10번 (10,000 / 1,000)

= 21번

 

 

보통 옵션값을 1,000 이상 주지는 않습니다.
in절 파라미터로 1,000 개 이상을 주었을때 너무 많은 in절 파라미터로 인해 문제가 발생할수도 있기 때문

 

 

Querydsl이라고해서 용빼는 재주가 있는게 아닙니다.

결국 Querydsl 역시 JPQL 로 변환 작업에서 동일하게 문제가 발생합니다.

Querydsl 역시 N 관계의 자식들에 대해서는 멀티 페치 조인이 안됩니다.

 

 

 

hibernate.default_batch_fetch_size를 글로벌 설정으로 사용해 N+1 문제를 최대한 in 쿼리로 기본적인 성능을 보장하게 한다.

@OneToOne, @ManyToOne과 같이 1 관계의 자식 엔티티에 대해서는 모두 Fetch Join을 적용하여 한방 쿼리를 수행한다.

@OneToMany, @ManyToMany와 같이 N 관계의 자식 엔티티에 관해서는 가장 데이터가 많은 자식쪽에 Fetch Join을 사용한다.

Fetch Join이 없는 자식 엔티티에 관해서는 위에서 선언한 hibernate.default_batch_fetch_size 적용으로 100~1000개의 in 쿼리로 성능을 보장한다.

'프로그래밍 > JPA' 카테고리의 다른 글

[Tutorial] JPA With Postgres and SpringBoot  (0) 2019.08.08
SpringBoot JPA (feat. h2 db)  (0) 2019.07.03