1 분 소요

지난 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절을 쓰는 걸 볼 수 있다.

이렇게 볼 수 있다.

  1. order 주문 리스트를 조회한다.
  2. orderItems 주문 아이템 리스트를 조회한다.
  3. 이때 order에서 가져온 id를 where 절에서 in절을 사용하여 한방에 찾아온다.
  4. 찾아온 orderItems를 Map 자료구조로 만든다
  5. order를 돌면서 orderItem을 세팅해 준다.
  • Query : 루트 1번 , 컬랙션 1번
  • ToOne 관계들을 먼저 조회하고, 여기서 얻은 식별자 orderId로 ToMany 관계인 OrderItem을 한꺼번에 조회
  • Map을 사용해서 매칭 성능 향상 (O(1))

태그: ,

카테고리:

업데이트:

댓글남기기