컬렉션 조회 최적화 2 - fetch join으로 최적화
OrderRepository에 finAllWithItem 추가
public List<Order> findAllWithItem() {
return em.createQuery("select o from Order o" +
" join fetch o.member m" +
" join fetch o.delivery d" +
" join fetch o.orderItems oi" +
" join fetch oi.item i", Order.class)
.getResultList();
}
컨트롤러
@GetMapping("/api/v3/orders")
public List<OrderDto> ordersV3() {
List<Order> orders = orderRepository.findAllWithItem();
List<OrderDto> collect = orders.stream()
.map(o -> new OrderDto(o))
.collect(Collectors.toList());
return collect;
}
fetch join으로 가져온 데이터들을 컨트롤러에서 넘겨준다. 이 때의 Dto는 이전의 Dto를 그대로 사용했다.
원래 같은 경우에는 Order만 쿼리로 조회하기 때문에 Lazy 로딩을 통해서 orderItem이나 delivery 등의 조회가 필요할 시 쿼리를 계속해서 내보냈기 때문에 성능이 좋지 않았다. 하지만 이번 방식은 fetch join을 사용하기 때문에 처음 쿼리에서 한 번에 필요한 데이터 그래프들을 모두 가져온다.
중요한건 저번 포스팅과 컨트롤러의 코드 차이는 아무것도 없다. 단지, repository의 메서드만 변경되었을 뿐인데 쿼리가 훨씬 적게 나가서 성능이 훨씬 좋다는 장점이 있다. 맨 아래에서 실제 api를 호출하면 나가는 쿼리를 첨부했다.
(하이버네이트 5까지는 order가 중복해서 4번이 나왔지만 하이버네이트 6부터는 fetch join 시에 자동으로 중복 제거를 해주기 때문에 이러한 점은 고려하지 않아도 된다.)
단점
일대 다 fetch join(컬렉션 페치 조인)를 하는 순간 페이징이 불가능해진다.
하이버네이트는 db에 쿼리를 내보내는 것이 아니라 메모리에 데이터를 가져온 다음에 처리를 한다. 데이터 양이 방대하다면 메모리 에러가 발생할 것이다.
public List<Order> findAllWithItem() {
return em.createQuery("select o from Order o" +
" join fetch o.member m" +
" join fetch o.delivery d" +
" join fetch o.orderItems oi" +
" join fetch oi.item i", Order.class)
.setFirstResult(1)
.setMaxResults(10)
.getResultList();
}
위의 쿼리에서 o.member이나 o.delivery나 oi.item i만 있을 때에는 상관 없다. 하지만 컬렉션 페치 조인인 o.orderItems oi 같은 경우에는 Order 입장에서 일대 다 fetch join이기 때문에 하이버네이트가 경고 로그를 남기면서 모든 데이터를 DB에서 읽어오고, 메모리에서 페이징 해버린다.
콘솔에 뜬 에러
WARN 14628 --- [nio-8080-exec-2] org.hibernate.orm.query : HHH90003004: firstResult/maxResults specified with collection fetch; applying in memory
실제로 api를 호출하면 나가는 쿼리
select
o1_0.order_id,
d1_0.delivery_id,
d1_0.city,
d1_0.street,
d1_0.zipcode,
d1_0.status,
m1_0.member_id,
m1_0.city,
m1_0.street,
m1_0.zipcode,
m1_0.name,
o1_0.order_date,
o2_0.order_id,
o2_0.order_item_id,
o2_0.count,
i1_0.item_id,
i1_0.dtype,
i1_0.name,
i1_0.price,
i1_0.stock_quantity,
i1_0.artist,
i1_0.etc,
i1_0.author,
i1_0.isbn,
i1_0.actor,
i1_0.director,
o2_0.order_price,
o1_0.status
from
orders o1_0
join
member m1_0
on m1_0.member_id=o1_0.member_id
join
delivery d1_0
on d1_0.delivery_id=o1_0.delivery_id
join
order_item o2_0
on o1_0.order_id=o2_0.order_id
join
item i1_0
on i1_0.item_id=o2_0.item_id