ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 컬렉션 조회 최적화 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

     

Designed by Tistory.