지금은 컬렉션도 각각 Dto로 조회해오면서, batch가 안 먹히게 되었다. (정확히 등록된 엔티티를 조회해와야만 되는 듯 하다(fetch 한것 등))
그래서 지금 컬렉션은 ToOne이 아니라서 row 뻥튀기를 일으키게 하지 않기 위해 따로 넣어줬다.
그래서 이렇게 OrderItem을 넣어줄 때마다 또 그 OrderItem을 조회해주는 쿼리가 n번 나가게 되었다.
이제는 저 OrderItem 조회하는 걸 쿼리를 한번만 나가게끔 만들 것이다.
@GetMapping("/api/v5/orders")
public List<OrderQueryDto> ordersV5(){
return orderQueryRepository.findAllByDto_optimization();
}
public List<OrderQueryDto> findAllByDto_optimization() {
List<OrderQueryDto> orders = findOrders();
List<Long> orderIds = toOrderIds(orders);
Map<Long, List<OrderItemQueryDto>> orderItemMap = findOrderItemMap(orderIds);
orders.forEach(o->o.setOrderItems(orderItemMap.get(o.getOrderId())));
return orders;
}
orders는 먼저 만들어 놨던
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();
}
이걸 이용한다. Dto로 한번에 ToOne인 것들 같이 받아오는.
orderId들을 따로 뽑는 이유는, sql 쿼리 중 in을 이용하기 위해 그렇다.
private List<Long> toOrderIds(List<OrderQueryDto> orders) {
return orders.stream()
.map(o -> o.getOrderId())
.collect(Collectors.toList());
}
그냥 map으로 order_id들만 뽑아서 List로 만드는 메소드
다음이 findOrderItemMap인데,
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" +
" where oi.order.id in :orderIds", OrderItemQueryDto.class
).setParameter("orderIds", orderIds)
.getResultList();
Map<Long, List<OrderItemQueryDto>> orderItemMap = orderItems.stream()
.collect(Collectors.groupingBy(orderItemQueryDto -> orderItemQueryDto.getOrderId()));
return orderItemMap;
}
먼저 Dto로 OrderItem들을 in으로 orderIds를 넣어서 조회해오고,
groupingBy는 말 그대로, 특정 값에 의해 묶을 수 있는 것인데,
Map<묶을기준값, 묶인데이터> 형식으로 반환이 된다.
지금 같은 경우는 orderItemQueryDto들을 orderId로 묶었다.
List인 orderItems를 stream() 돌려서 각각의 원소를 그룹핑 해 주는데, orderItemQueryDto를 orderItemQueryDto의 orderId를 기준으로 묶는 것이다.
그렇게 각각의 orderItemQueryDto가, List<OrderItemQueryDto> 형태로 orderId 기준으로 묶인다.
그렇게 묶은 후, 그 Map<Long, List<OrderItemQueryDto>> 자체를 반환해 준다.
Long이 기준이 된 orderId다.
그러면 저기서 또 orders에다 forEach돌리는데,
각각의 order에다가 setOrderItems를 해 주는데, 그거를 반환받은 Map<Long, List<OrderItemQueryDto>>에서 order_id로 get해서 나온 List<OrderItemQueryDto>를 넣어 준다.
그냥 map형식의 콜렉션에서 get 해온거다.
여튼 그렇게 order가 완성이 된다.
즉, 먼저 ToOne인 것들은 다 가져오고,
order의 id들은 따로 뽑아서 List로 만든 다음 활용한다.
OrderItems들을 저 따로 뽑은 id들을 in의 파라미터로 넣어 조회해온다.
그 다음, 그렇게 뽑아놓은 OrderItems 뭉치들을 order_id 기준으로 나눠서 분리해 놓는다.
Map으로 하니 편하다.
Map 자체가 메모리에서 공간을 잡아두는 것이 아닌 형식을 만들어 두는 것이다.
그래서 처음 저런 식이면
HashMap<T, K> hashMap = new HashMap<>();
for(OrderItem orderItem : orderItems){
if(hashMap.get(orderItem.orderId) == null){
haspMap.put(orderItem.orderId, new ArrayList<OrderItem>);
}
hashMap.get(orderItem.orderId).add(orderItem);
}
return hashMap;
groupingBy는 이런 식일 것이다.
여튼 저렇게 해서 받아 놓은 걸 order들에게 set 해놓고, 그 order들을 반환한다.
이러면 이제 OrderItem들을 조회할 때 쿼리를 in으로 묶어서 조회할 수 있다.
그러면 이제
select
o1_0.order_id,
m1_0.name,
o1_0.order_date,
o1_0.status,
d1_0.city,
d1_0.street,
d1_0.zipcode
from
orders o1_0
join
member m1_0
on m1_0.member_id=o1_0.member_id
join
delivery d1_0
on d1_0.delivery_id=o1_0.delivery_id
2023-11-16T10:51:56.306+09:00 INFO 7652 --- [nio-8080-exec-2] p6spy : #1700099516306 | took 0ms | statement | connection 22| url jdbc:h2:tcp://localhost/./jpashop
select o1_0.order_id,m1_0.name,o1_0.order_date,o1_0.status,d1_0.city,d1_0.street,d1_0.zipcode from orders o1_0 join member m1_0 on m1_0.member_id=o1_0.member_id join delivery d1_0 on d1_0.delivery_id=o1_0.delivery_id
select o1_0.order_id,m1_0.name,o1_0.order_date,o1_0.status,d1_0.city,d1_0.street,d1_0.zipcode from orders o1_0 join member m1_0 on m1_0.member_id=o1_0.member_id join delivery d1_0 on d1_0.delivery_id=o1_0.delivery_id;
2023-11-16T10:51:56.309+09:00 DEBUG 7652 --- [nio-8080-exec-2] org.hibernate.SQL :
select
o1_0.order_id,
i1_0.name,
o1_0.order_price,
o1_0.count
from
order_item o1_0
join
item i1_0
on i1_0.item_id=o1_0.item_id
where
o1_0.order_id in (?,?)
쿼리가 2개가 나간다.
처음 ToOne인거 모아서 new Operation Dto로 조회한 거 하나랑,
컬렉션들인 OrderItems를 따로 모아서 in으로 조회한 거 (아이템은 OrderItem 입장에서 ToOne이니 같이 조회함.)
'스프링데이터 + JPA > API 개발' 카테고리의 다른 글
17. OSIV 성능 최적화 (0) | 2023.11.17 |
---|---|
16. Dto로 조회. 플랫 데이터 최적화 쿼리 하나 (0) | 2023.11.16 |
14. 컬렉션 Dto로 직접 조회 (0) | 2023.11.16 |
13. 컬렉션 페이징 한계돌파 (0) | 2023.11.15 |
12. 컬렉션 -> 엔티티 join fetch 최적화 (0) | 2023.11.15 |