컬렉션 조회 - v6 JPA에서 DTO 직접 조회, 플랫 데이터 최적화
v5 에 이어서 이젠 쿼리를 한방으로 줄여보자.
컨트롤러를 보자
1
2
3
4
5
6
7
8
9
10
11
@GetMapping("/api/v6/orders")
public List<OrderQueryDto> ordersV6() {
List<OrderFlatDto> flats = orderQueryRepository.findAllByDto_flat();
return flats.stream()
.collect(groupingBy(o -> new OrderQueryDto(o.getOrderId(), o.getName(), o.getOrderDate(), o.getOrderStatus(), o.getAddress()),
mapping(o -> new OrderItemQueryDto(o.getOrderId(), o.getItemName(), o.getOrderPrice(), o.getCount()), toList())
)).entrySet().stream()
.map(e -> new OrderQueryDto(e.getKey().getOrderId(), e.getKey().getName(), e.getKey().getOrderDate(), e.getKey().getOrderStatus(), e.getKey().getAddress(), e.getValue()))
.collect(toList());
}
괴랄한 return 문이 나온다.
일단 repository를 보자
1
2
3
4
5
6
7
8
9
10
public List<OrderFlatDto> findAllByDto_flat() {
return em.createQuery(
"select new jpabook.jpashop.repository.order.query.OrderFlatDto(o.id, m.name, o.orderDate, o.status, d.address, i.name, oi.orderPrice, oi.count)" +
" from Order o " +
" join o.member m " +
" join o.delivery d" +
" join o.orderItems oi " +
" join oi.item i", OrderFlatDto.class)
.getResultList();
}
다음과 같이 한방에 모든 걸 다 join 해서 가져오게 된다. OrderFlatDto라는 걸 또 만들어서 거기다 모든걸 다 때려 박았다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@Data
public class OrderFlatDto {
private Long orderId;
private String name;
private LocalDateTime orderDate;
private OrderStatus orderStatus;
private Address address;
private String itemName;
private int orderPrice;
private int count;
public OrderFlatDto(Long orderId, String name, LocalDateTime orderDate, OrderStatus orderStatus, Address address, String itemName, int orderPrice, int count) {
this.orderId = orderId;
this.name = name;
this.orderDate = orderDate;
this.orderStatus = orderStatus;
this.address = address;
this.itemName = itemName;
this.orderPrice = orderPrice;
this.count = count;
}
}
그런데 이렇게 되면 toMany를 조인했기 때문에 데이터가 뻥튀기가 된다.
다음과 같이 2개의 order가 2번나온다.
이제 이거를 애플리케이션 상에서 다시 분해하고 조립해야 하는 것이다. 그래서 컨트롤러에서 저 괴랄한 리턴이 나오게 된 것이다. 그럼 return 부터 다시 보자.
1
2
3
4
5
6
return flats.stream()
.collect(groupingBy(o -> new OrderQueryDto(o.getOrderId(), o.getName(), o.getOrderDate(), o.getOrderStatus(), o.getAddress()),
mapping(o -> new OrderItemQueryDto(o.getOrderId(), o.getItemName(), o.getOrderPrice(), o.getCount()), toList())
)).entrySet().stream()
.map(e -> new OrderQueryDto(e.getKey().getOrderId(), e.getKey().getName(), e.getKey().getOrderDate(), e.getKey().getOrderStatus(), e.getKey().getAddress(), e.getValue()))
.collect(toList());
하나하나 따져 보자
flats.stream(): flats라는 List에서 Stream을 생성groupingBy: Stream을 그룹화. 이때, 그룹화 기준은 OrderQueryDto(@EqualsAndHashCode(of = “orderId”)) 객체를 생성하여 사용.mapping: 각 그룹에 속하는 Order 객체를 OrderItemQueryDto 객체로 매핑.entrySet().stream(): groupingBy를 통해 생성된 Map의 EntrySet을 다시 Stream으로 변환.map: 각 Entry의 Key와 Value를 사용하여 OrderQueryDto 객체를 생성.collect(toList()): 최종적으로 생성된 OrderQueryDto 객체들을 List로 수집.
@EqualsAndHashCode
롬복에서 지원하는 이름에서도 알 수 있듯이 equals, hashCode 자동 생성해주는 애노테이션.
equals, hashCode 가 없으면 객체의 필드 값을 key로 grouping 할 것이기 때문에 의도한 대로 grouping이 안될 수 있음.
다음과 같이 안해주면
전혀 예상 못한 따로 따로 놀게 됨.
스트림에 여러 기능들을 더 공부 해봐야 겠다.. ㅠㅠ
아무튼 이런 과정을 거치게 되면
다음과 같이 쿼리도 1번, 결과도 잘 나오게 된 것을 볼 수 있다.
- Query : 1번
- 단점
- 쿼리는 한번이지만 조인으로 인해 DB에서 애플리케이션에 전달하는 데이터에 중복 데이터가 추가되므로 상황에 따라 V5 다 느릴 수 있다.
- 애플리케이션에서 추가 작업이 크다.
- 페이징 불가
빨리 QueryDsl 공부 해야겠다..
댓글남기기