컬렉션 조회 - v5 JPA에서 DTO 직접 조회-컬렉션 최적화.
지난 v4 에서는 N+1쿼리 를 직접 만들어 봤다. 오늘은 1+1로 쿼리를 두 번만 나가게 최적화를 진행해 보자.
먼저 v5 컨트롤러를 만들자
1
2
3
4
@GetMapping("/api/v5/orders")
public List<OrderQueryDto> ordersV5() {
return orderQueryRepository.findAllByDto_optimization();
}
레포지토리에 findAllByDto_optimization 메서드를 만들겠다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public List<OrderQueryDto> findAllByDto_optimization() {
List<OrderQueryDto> result = findOrders(); // 먼저 order를 가져온다. 쿼리 1
List<Long> orderIds = result.stream() // 그다음에 id를 리스트에서 뽑아온다
.map(OrderQueryDto::getOrderId)
.toList();
List<OrderItemQueryDto> orderItems = em.createQuery( // 그다음에 orderItem을 가져온다. 쿼리 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) // where 절에 in절로 id가 들어간다!!
.getResultList();
// 가져온 item 쿼리를 key가 Id, value가 아이템리스트인 Map 자료구조에 넣는다.
Map<Long, List<OrderItemQueryDto>> orderItemMap = orderItems.stream()
.collect(Collectors.groupingBy(OrderItemQueryDto::getOrderId));
// order 를 돌면서 orderItem을 세팅해줄껀데 그건 위에서 만든 맵에 key랑 같은 orderItems를 세팅해 줄꺼다.
result.forEach(o -> o.setOrderItems(orderItemMap.get(o.getOrderId())));
return result;
}
쿼리가 2번 나가고, item을 가져올 때 in절을 쓰는 걸 볼 수 있다.
이렇게 볼 수 있다.
- order 주문 리스트를 조회한다.
- orderItems 주문 아이템 리스트를 조회한다.
- 이때 order에서 가져온 id를 where 절에서 in절을 사용하여 한방에 찾아온다.
- 찾아온 orderItems를 Map 자료구조로 만든다
- order를 돌면서 orderItem을 세팅해 준다.
- Query : 루트 1번 , 컬랙션 1번
- ToOne 관계들을 먼저 조회하고, 여기서 얻은 식별자 orderId로 ToMany 관계인
OrderItem을 한꺼번에 조회 - Map을 사용해서 매칭 성능 향상 (O(1))
댓글남기기