주문 조회 V5: JPA에서 DTO 직접 조회 - 컬렉션 조회 최적화

이전 버전과 차이점(핵심)

Before : foreach 돌 때마다 쿼리문 실행 After : in 절로 메모리에 데이터 한번에 가져온 후, foreach 돌 때마다 메모리에서 값 매칭되는 데이터 셋팅 =>쿼리 2번으로 최적화 가능!

  1. OrderApiController

  2. OrderQueryRepository findAllByDto_optimization(메인) findOrderItemMap//리팩토링 결과 생성된 메서드

    toOrderIds//리팩토링 결과 생성된 메서드

OrderApiController

@GetMapping("/api/v5/orders")
public List<OrderQueryDto> ordersV5(){
    return orderQueryRepository.findAllByDto_optimization();
}

OrderQueryRepository

  1. findAllByDto_optimization(메인) 일단 findOrders()로 Member & Delivery 조인해서 한번에 Order 조회하는 것은 동일하다.

public List<OrderQueryDto> findAllByDto_optimization() {
        List<OrderQueryDto> result = findOrders();
        Map<Long, List<OrderItemQueryDto>> orderItemMap = findOrderItemMap(toOrderIds(result));
        result.forEach(o -> o.setOrderItems(orderItemMap.get(o.getOrderId())));
        return result;
}

2. findOrderItemMap(List<Long> orderIds) OrderItem 조회쿼리 앞과 비슷하지만 여기서 핵심은 in절로 orderId 하나씩 조회하는 게 아니라 한번에 다 가져온다?! 그래서 orderIds 를 orderItems에 저장한다.

orderItems의 id, orderItemDTO를 Map으로 저장=> key로 조회 시 O(1)으로 성능 최적화&코드 간결❤️

private Map<Long, List<OrderItemQueryDto>> findOrderItemMap(List<Long> orderIds) {
    List<OrderItemQueryDto> orderItems = em.createQuery("select new jpabook_v2.jpashop_v2.repository.order.query.OrderItemQueryDto(oi.order.id, i.name, oi.orderPrice, oi.count)" +
            " from OrderItem oi" +
            " join oi.item i" +
            " where oi.order.id in :orderIds", OrderItemQueryDto.class)
            .setParameter("orderIds", orderIds)//findOrders()로 가져온 List<OrderQueryDto>를 stream, map으로 돌려서 id를 다 뽑는다!
            .getResultList();

    Map<Long, List<OrderItemQueryDto>> orderItemMap = orderItems.stream()
            .collect(Collectors.groupingBy(orderItemQueryDto -> orderItemQueryDto.getOrderId()));
    return orderItemMap;
}

3. toOrderIds(List<OrderQueryDto> result) findOrders()로 조회한 List<OrderQueryDto>를 foreach로 각각의 Id들을 리스트로 만들어서 반환

private List<Long> toOrderIds(List<OrderQueryDto> result) {
    List<Long> orderIds = result.stream()
            .map(o -> o.getOrderId())
            .collect(Collectors.toList());
    return orderIds;
}

fetch join과 트레이드 오프

- : JPA에서 DTO 직접 조회 시, 상당히 많은 쿼리들을 직접 작성 + : select쿼리문이 fetch join에 비해 상당히 적다!!

결론, 정리

  • 쿼리 : root 1번, 컬렉션 1번(총 2번)

  • ToOne 관계들 먼저 join으로 조회하고, 여기서 얻은 식별자 orderId로 ToMany관계인 orderItem을 한번에 조회

  • MAP을 사용해서 매칭 성능 향상(O(1))

Last updated