-
컬렉션 조회 최적화 2 - fetch join으로 최적화JPA/JPA 활용 2023. 8. 10. 16:35
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
'JPA > JPA 활용' 카테고리의 다른 글
OSIV와 성능 최적화 (0) 2023.08.11 컬렉션 조회 최적화 3 - 페이징과 한계점 돌파 (0) 2023.08.10 컬렉션 조회 최적화 1 - 페치 조인을 안 했을 시 (0) 2023.08.10 간단한 주문조회 API - 지연 로딩과 조회 성능 최적화(2) (0) 2023.07.11 간단한 주문조회 API - 지연 로딩과 조회 성능 최적화 (0) 2023.07.11