2 분 소요

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());  

하나하나 따져 보자

  1. flats.stream() : flats라는 List에서 Stream을 생성
  2. groupingBy : Stream을 그룹화. 이때, 그룹화 기준은 OrderQueryDto(@EqualsAndHashCode(of = “orderId”)) 객체를 생성하여 사용.
  3. mapping : 각 그룹에 속하는 Order 객체를 OrderItemQueryDto 객체로 매핑.
  4. entrySet().stream() : groupingBy를 통해 생성된 Map의 EntrySet을 다시 Stream으로 변환.
  5. map : 각 Entry의 Key와 Value를 사용하여 OrderQueryDto 객체를 생성.
  6. collect(toList()) : 최종적으로 생성된 OrderQueryDto 객체들을 List로 수집.

@EqualsAndHashCode

롬복에서 지원하는 이름에서도 알 수 있듯이 equalshashCode 자동 생성해주는 애노테이션. equalshashCode 가 없으면 객체의 필드 값을 key로 grouping 할 것이기 때문에 의도한 대로 grouping이 안될 수 있음. 다음과 같이 안해주면 전혀 예상 못한 따로 따로 놀게 됨.

스트림에 여러 기능들을 더 공부 해봐야 겠다.. ㅠㅠ

아무튼 이런 과정을 거치게 되면 다음과 같이 쿼리도 1번, 결과도 잘 나오게 된 것을 볼 수 있다.

  • Query : 1번
  • 단점
    • 쿼리는 한번이지만 조인으로 인해 DB에서 애플리케이션에 전달하는 데이터에 중복 데이터가 추가되므로 상황에 따라 V5 다 느릴 수 있다.
    • 애플리케이션에서 추가 작업이 크다.
    • 페이징 불가

빨리 QueryDsl 공부 해야겠다..

태그: ,

카테고리:

업데이트:

댓글남기기