컬렉션 조회 최적화 - JPA에서 DTO 직접 조회 + 컬렉션 조회 최적화

HotFried·2023년 11월 27일
0

이전 글 : 컬렉션 조회 최적화 - JPA에서 DTO 직접 조회

JPA에서 DTO를 직접 조회 시 N+1 쿼리 문제가 발생하는 것을 확인하였다.
이번에 어떻게 문제를 해결할 수 있을지 알아보도록 한다.


해결방법

IN절을 사용하자

@Repository
@RequiredArgsConstructor
public class OrderQueryRepository {
    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;
    }

    private List<OrderQueryDto> findOrders() {
        return em.createQuery(
                        "select new jpabook.jpashop.repository.order.query.OrderQueryDto(o.id, m.name,o.orderDate,o.status,d.address)"
                                + " from Order o" +
                                " join o.member m" +
                                " join o.delivery d", OrderQueryDto.class)
                .getResultList();
    }

    private Map<Long, List<OrderItemQueryDto>> findOrderItemMap(List<Long> orderIds) {
        List<OrderItemQueryDto> orderItems = em.createQuery(
                        "select new jpabook.jpashop.repository.order.query.OrderItemQueryDto(oi.order.id, i.name, oi.orderPrice, oi.count)"
                                +
                                " from OrderItem oi" +
                                " join oi.item i" +
                                //IN절을 사용하여 성능 최적화
                                " where oi.order.id in :orderIds", OrderItemQueryDto.class)
                .setParameter("orderIds", orderIds)
                .getResultList();

        return changeToMap(orderItems);
    }

    private Map<Long, List<OrderItemQueryDto>> changeToMap(List<OrderItemQueryDto> orderItems) {
        return orderItems.stream()
                .collect(Collectors.groupingBy(OrderItemQueryDto::getOrderId));
    }

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

위 코드 중 findOrderItemMap을 살펴보자

" where oi.order.id in :orderIds"

앞선 글에서 =을 사용한 것과 달리 In연산자를 활용해 Dto를 반환하고 있는 것을 확인할 수 있다.

메모리상에서 map으로 변환 후 값을 매칭해서 가져오기 때문에 N번의 쿼리 대신 1번의 쿼리로 최적화를 할 수 있다.


정리

  • IN절을 이용하여 orderItems를 한번에 조회
    -> N번의 쿼리 대신 1번의 쿼리로 최적화를 할 수 있다.

  • map은 key를 가지고있기 때문에 조회 속도가 O(1)로 빠르다.
    -> map 자료구조 이용

  • 쿼리는 총 2번 나간다.
    -> 루트 쿼리 뒤에 orderItem을 가져오는 쿼리가 한 번만 실행된다.
    (한번에 조회 = IN절을 이용했기 때문)


참고 :

실전! 스프링 부트와 JPA 활용2 - API 개발과 성능 최적화

profile
꾸준하게

0개의 댓글