프로그래밍/spring boot

[스프링부트] 실전! 스프링 부트와 JPA 활용2 컬렉션 조회 최적화 #5 JPA에서 DTO 직접 조회 - 컬렉션 조회 최적화

aSpring 2023. 11. 26. 16:43
728x90
728x90
※ 본 포스팅은 김영한 강사님의 인프런 '실전! 스프링 부트와 JPA 활용2 - API 개발과 성능 최적화' 강의를 들으며 작성한 수강일지 입니다.

 
 

 

 

| API 개발 고급 - 컬렉션 조회 최적화

1. 주문 조회 V1: 엔티티 직접 노출
2. 주문 조회 V2: 엔티티를 DTO로 변환
3. 주문 조회 V3: 엔티티를 DTO로 변환 - 페치 조인 최적화
4. 주문 조회 V3.1: 엔티티를 DTO로 변환 - 페이징과 한계 돌파
5. 주문 조회 V4: JPA에서 DTO 직접 조회
6. 주문 조회 V5: JPA에서 DTO 직접 조회 - 컬렉션 조회 최적화 
7. 주문 조회 V6: JPA에서 DTO로 직접 조회, 플랫 데이터 최적화
8. API 개발 고급 정리

 

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

OrderApiController에 추가

// v5 - JPA에서 DTO 직접 조회
@GetMapping("/api/v5/orders")
public List<OrderQueryDto> ordersV5() {
    return orderQueryRepository.findAllByDto_optimization();
}

 

OrderQueryRepository에 추가

public List<OrderQueryDto> findAllByDto_optimization() {
    List<OrderQueryDto> result = findOrders(); // Query 1번

//        // orderId의 list -> 주문번호 4, 11번이 들어있을 것
//        List<Long> orderIds = toOrderIds(result);
//        // 이전 v4 단점 -> loop를 돔
//        Map<Long, List<OrderItemQueryDto>> orderItemMap = findOrderItemMap(orderIds);

    Map<Long, List<OrderItemQueryDto>> orderItemMap = findOrderItemMap(toOrderIds(result)); // 주문 데이터 만큼 한방에 메모리에 올리고

    result.forEach(o -> o.setOrderItems(orderItemMap.get(o.getOrderId()))); // loop 돌면서 모자란 부분 채워넣고

    return result;
}

private Map<Long, List<OrderItemQueryDto>> findOrderItemMap(List<Long> orderIds) {
    List<OrderItemQueryDto> orderItems = em.createQuery( // Query 1번  --> 총 2번 나감
            "select new jpabook.jpashop.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)
            .getResultList();

    // 이렇게 하면 쿼리 2번으로 최적화 가능
    Map<Long, List<OrderItemQueryDto>> orderItemMap = orderItems.stream()
            .collect(Collectors.groupingBy(OrderItemQueryDto::getOrderId));// orderIdf를 기준으로 Map으로 변경
    return orderItemMap;
}

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

 

  • Query: 루트 1번, 컬렉션 1번
  • ToOne 관계들을 먼저 조회하고, 여기서 얻은 식별자 orderId로 ToMany 관계인 OrderItem 을 한꺼번에 조회
  • MAP을 사용해서 매칭 성능 향상(O(1))

 

 

728x90
728x90