JPA에서 일대다 관계일 때, fetch join을 사용하면 데이터가 뻥튀기된다.
@Query("select o from Order o join fetch o.orderItems oi")
JPA의 distinct를 사용하면 데이터 뻥튀기를 어느 정도 해결 가능하다.
@Query("select distinct o from Order o join fetch o.orderItems oi")
distinct를 사용하는 방법은 DB에서는 뻥튀기된 값을 가져와서, JPA가 Order의 id가 같은 것을 제거해 준다.
이러한 방법은 페이징이 불가능하다.
DB에서 뻥튀기된 값을 모두 메모리에 올린 다음 메모리에서 정렬을 거쳐 페이징을 하게 된다.
가져오는 데이터가 많다면 Out of Memory가 발생할 수 있다.
또한, 컬렉션(일대다) 페치조인은 1개만 사용할 수 있다.
해결 방법
그렇다면, 컬렉션 엔티티를 조회할 때 페이징을 어떻게 해야 할까?
1. ToOne(OneToOne, ManyToOne) 관계는 모두 페치 조인으로 데이터를 가져온다.
@Query("select o from Order o join fetch o.member m join fetch o.delivery d")
2. 컬렉션은 지연 로딩으로 조회한다. 즉, 페치 조인을 사용하지 않고 데이터를 가져온다.
3. 지연 로딩 성능 최적화를 위해 hibernate.default_batch_fetch_size, @BatchSize를 적용한다.
spring:
jpa:
properties:
hibernate:
default_batch_fetch_size: 100
이 옵션을 사용하면 컬렉션이나, 프록시 객체를 한꺼번에 설정한 크기만큼 IN 쿼리로 조회한다.
장점
쿼리 호출 수가 1+N에서 1+1로 최적화된다.
페치 조인 방식과 비교해서 쿼리 호출 수는 약간 증가하지만, DB 데이터 전송량이 감소한다.
컬렉션 페치 조인은 페이징이 불가능하지만, 이 방법은 페이징이 가능하다.
권장 순서
실무에서 권장하는 순서는 다음과 같다고 한다.
1. 엔티티 조회 방식으로 우선 접근
a. 페치 조인으로 쿼리 수를 최적화
b. 컬렉션 최적화
i. 페이징 필요 -> hibernate.default_batch_fetch_size, @BatchSize로 최적화
ii. 페이징 필요 X -> 페치 조인 사용
2. 엔티티 조회 방식으로 해결이 안 되면 DTO 조회 방식 사용
3. DTO 조회 방식으로도 해결이 안 된다면, NativeSQL or JdbcTemplate 사용
'Spring' 카테고리의 다른 글
@Transactional(readOnly=true) (0) | 2023.08.04 |
---|---|
[JPA] OSIV (0) | 2023.03.21 |
[JPA] N+1 문제 (0) | 2023.01.08 |
@RequestMapping (0) | 2022.09.12 |
Spring MVC (0) | 2022.09.08 |