컬렉션을 페치 조인하면 페이징이 불가능하다.
- 컬렉션을 페치조인하면 일대다 조인이 발생하므로 데이터가 예측할 수 없이 증가한다.
- 일대다에서 일(1)을 기준으로 페이징을 하는 것이 목적이다. 그렇데 데이터는 다(N)를 기준으로 row가 생성된다.
- Order를 기준으로 페이징하고 싶은데 다(N)인 OrderItem을 조인하면 OrderItem이 기준이 되어버린다.
- 더 자세한 내용은 자바 ORM 표준 JPA 프로그래밍 - 페치 조인 한계 참고
이 경우 하이버네이트는 경고 로그를 남기고 모든 DB 데이터를 읽어서 메모리에서 페이징을 시도한다. 최악의 경우 장애로 이어질 수 있다.
한계 돌파
그러면 페이징과 컬렉션 엔티티를 함께 조회하려면 어떻게 해야할까 ?
지금부터 코드도 단순하고, 성능 최적화도 보장하는 매우 강력한 방법을 소개하겠다.
대부분의 페이징 + 컬렉션 엔티티 조회 문제는 이 방법으로 해결할 수 있다.
- 먼저 ToOne 관계를 모두 페치조인한다. ToOne관계는 row 수를 증가시키지 않으므로 페이징 쿼리에 영향을 주지 않는다.
- 컬렉션은 지연 로딩으로 조회한다.
- 지연 로딩 성능 최적화를 위해 hibernate.default_batch_fetch_size @BatchSize 를 적용한다.
- **hibernate.default_batch_fetch_size : 글로벌 설정**
- **@BatchSize : 개별 최적화 - 디테일하게 적용하고 싶을 때**
- 이 옵션을 사용하면 컬렉션이나, 프록시 객체를 한꺼번에 설정한 size만큼 IN 쿼리로 조회한다.
jpa: hibernate: ddl-auto: create properties: hibernate: show_sql: true format_sql: true default_batch_fetch_size: 1000 #최적화 옵션
- 개별로 설정하려면 @BatchSize를 적용하면 된다 (컬렉션은 컬렉션 필드에, 엔티티는 엔티티 클래스에 적용)
- 쿼리 호출 수 가 1+N → 1+1로 최적화된다
- 조인보다 DB 데이터 전송량이 최적화된다.이 방법은 각각 조회하므로 전송해야할 중복 데이터가 없다
- Order와 OrderItem을 조인하면 Order가 OrderItem만큼 중복해서 조회된다.
- 페치 조인 방식과 비교해서 쿼리 호출 수가 약간 증가하지만, DB 데이터 전송량이 감소한다.
- 컬렉션 페치 조인은 페이징이 불가능하지만 이 방법은 페이징이 가능하다
- ToOne 관계는 페치 조인해도 페이징에 영향을 주지 않는다. 따라서 ToOne관계는 페치조인으로 쿼리 수를 줄이고 해결하고, 나머지는 hibernate.default_batch_fetch_size 로 최적화하자
- 한번에 조회
- 데이터가 최종 갯수만큼 나오는데 중복인 된 상태에서 조회가 된다
- DB에서 애플리케이션으로 전부 전송하게 되어, 전송량 자체가 많아지게 된다
- 용량이 많은 이슈
public List<Order> findAllByMemberDelivery(int offeset, int limit) { return em.createQuery( "select o from Order o"+ " join fetch o.member m "+ " join fetch o.delivery d ",Order.class) .setFirstResult(offeset) .setMaxResults(limit) .getResultList(); }
- 데이터 전송량이 쿼리가 최적화되어서 전송되기때문에 용량이슈가 발생하지 않는다
- 데이터는 중복없이 조회가 가능하다
- 정확하게 필요한 데이터를 조회하게 된다.
public List<Order> findAllByMemberDelivery(int offeset, int limit) { return em.createQuery( "select o from Order o",Order.class) .setFirstResult(offeset) .setMaxResults(limit) .getResultList(); }
- batch size 전략때문에 성능 최적화가 된 상태에서 데이터를 가져올 수 있다.
- 아무래도 네트워크를 많이 타서 가져오게 되므로 fetch join을 미리 잡아놓고 가져오는게 효율적이다
'TIL' 카테고리의 다른 글
[JPA] OSIV와 성능 최적화 (0) | 2023.03.22 |
---|---|
[JPA] API 개발 고급 정리 (1) | 2023.03.21 |
[JPA] N+1 문제 (0) | 2023.03.19 |
[JPA] respository에 DTO 사용 ? (0) | 2023.03.18 |
[JPA] DTO 설계 이유 (0) | 2023.03.18 |