이전 글 : 컬렉션 조회 최적화 - JPA에서 DTO 직접 조회
JPA에서 DTO를 직접 조회 시 N+1
쿼리 문제가 발생하는 것을 확인하였다.
이번에 어떻게 문제를 해결할 수 있을지 알아보도록 한다.
@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절을 이용했기 때문)
참고 :