오봉이와 함께하는 개발 블로그

JPA 2 - 주문 조회 V6 : JPA에서 DTO로 직접 조회(플랫 데이터 최적화) 본문

BE/JPA

JPA 2 - 주문 조회 V6 : JPA에서 DTO로 직접 조회(플랫 데이터 최적화)

오봉봉이 2022. 9. 6. 23:14
728x90

주문 조회 V6: JPA에서 DTO로 직접 조회, 플랫 데이터 최적화

조회 V6

OrderApiController에 추가

@GetMapping("/api/v6/orders")
public List<OrderFlatDto> ordersV6() {
    return orderQueryRepository.findAllByDto_flat();
}

OrderQueryRepository에 추가

    public List<OrderFlatDto> findAllByDto_flat() {
        return em.createQuery(
                        "select new jpabook.jpashop.repository.order.query.OrderFlatDto" +
                                "(o.id, m.name, o.orderDate, d.address, o.status, 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();
    }

OrderFlatDto

@Data
public class OrderFlatDto {
    private Long orderId;
    private String name;
    private LocalDateTime orderDate;
    private Address address;
    private OrderStatus orderStatus;

    // OrderItem
    private String itemName;//상품 명
    private int orderPrice; //주문 가격
    private int count; //주문 수량

    public OrderFlatDto(Long orderId, String name, LocalDateTime orderDate, Address address, OrderStatus orderStatus, String itemName, int orderPrice, int count) {
        this.orderId = orderId;
        this.name = name;
        this.orderDate = orderDate;
        this.address = address;
        this.orderStatus = orderStatus;
        this.itemName = itemName;
        this.orderPrice = orderPrice;
        this.count = count;
    }
}

Order와 OrderItem을 조인하고, OrderItem과 Item을 조인해서 한방 쿼리로 데이터를 가져온다.
데이터를 한 row에 담기 때문에 원하는 데이터와 DTO의 구조를 맞춰야 한다.

결과

위 코드를 실행하면 아래와 같은 결과가 나온다.

[
    {
        "orderId": 4,
        "name": "userA",
        "orderDate": "2022-09-06T21:57:57.690991",
        "address": {
            "city": "서울",
            "street": "1",
            "zipcode": "1111"
        },
        "orderStatus": "ORDER",
        "itemName": "JPA1 BOOK",
        "orderPrice": 10000,
        "count": 1
    },
    {
        "orderId": 4,
        "name": "userA",
        "orderDate": "2022-09-06T21:57:57.690991",
        "address": {
            "city": "서울",
            "street": "1",
            "zipcode": "1111"
        },
        "orderStatus": "ORDER",
        "itemName": "JPA2 BOOK",
        "orderPrice": 20000,
        "count": 2
    },
    {
        "orderId": 11,
        "name": "userB",
        "orderDate": "2022-09-06T21:57:57.745537",
        "address": {
            "city": "진주",
            "street": "2",
            "zipcode": "2222"
        },
        "orderStatus": "ORDER",
        "itemName": "SPRING1 BOOK",
        "orderPrice": 20000,
        "count": 3
    },
    {
        "orderId": 11,
        "name": "userB",
        "orderDate": "2022-09-06T21:57:57.745537",
        "address": {
            "city": "진주",
            "street": "2",
            "zipcode": "2222"
        },
        "orderStatus": "ORDER",
        "itemName": "SPRING2 BOOK",
        "orderPrice": 40000,
        "count": 4
    }
]

데이터가 중복으로 출력이 된다.
위 쿼리의 장점은 쿼리가 한 번만 나간다는 점이다.
단점은 Order를 기준으로 페이징을 할 수 없고, OrderItem 기준으로 페이징이 된다는 점이다.


조회 V6.1

OrderQueryDto 구조로 API 스펙이 변경되었다.
그럼 어떻게 해야 할까?
내가 직접 중복을 걸러내면 된다.
OrderFlatDto에는 orderId가 있기 때문에 중복인 경우 걸러내고 OrderFlatDto를 루프 돌려서 OrderQueryDtoOrderItemQueryDto에 맞게 데이터를 재구성하면 된다.

Controller

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

flats을 통해 OrderQueryDto, OrderItemQueryDto하나로 묶어서 컬렉션으로 만든다.
만든 컬렉션을 통해 최종적으로 OrderQueryDto의 생성자로 OrderQueryDto를 만들어서 리스트로 반환한다.

OrderQueryDto에 생성자 추가

@EqualsAndHashCode(of = "orderId")
public class OrderQueryDto {

    //..............

    public OrderQueryDto(Long orderId, String name, LocalDateTime orderDate,
                         OrderStatus orderStatus, Address address, List<OrderItemQueryDto> orderItems) {
        this.orderId = orderId;
        this.name = name;
        this.orderDate = orderDate;
        this.orderStatus = orderStatus;
        this.address = address;
        this.orderItems = orderItems;
    }
}

orderId를 기준으로 .collect(groupingBy...)의 기준을 잡아주기 위해 @EqualsAndHashCode(of = "orderId")를 명시해주자.

결과

[
    {
        "orderId": 11,
        "name": "userB",
        "orderDate": "2022-09-06T23:07:12.354101",
        "orderStatus": "ORDER",
        "address": {
            "city": "진주",
            "street": "2",
            "zipcode": "2222"
        },
        "orderItems": [
            {
                "itemName": "SPRING1 BOOK",
                "orderPrice": 20000,
                "count": 3
            },
            {
                "itemName": "SPRING2 BOOK",
                "orderPrice": 40000,
                "count": 4
            }
        ]
    },
    {
        "orderId": 4,
        "name": "userA",
        "orderDate": "2022-09-06T23:07:12.296424",
        "orderStatus": "ORDER",
        "address": {
            "city": "서울",
            "street": "1",
            "zipcode": "1111"
        },
        "orderItems": [
            {
                "itemName": "JPA1 BOOK",
                "orderPrice": 10000,
                "count": 1
            },
            {
                "itemName": "JPA2 BOOK",
                "orderPrice": 20000,
                "count": 2
            }
        ]
    }
]

11번 부터 출력되지만, sorting을 통해 해결할 수 있을 것이다.

정리

  • 장점
    • Query가 1번 나간다.
  • 단점
    • 쿼리는 한번이지만 조인으로 인해 DB에서 애플리케이션에 전달하는 데이터에 중복 데이터가 추가되므로 상황에 따라 V5 보다 느릴 수도 있다.
    • 애플리케이션에서 추가 작업이 크다
    • 데이터가 중복되기 때문에 Order를 기준으로 페이징이 불가능하다.
      • OrderItem을 기준으로는 페이징을 할 수 있지만, OrderItem 기준으로 페이징 할 일이..?
인프런 김영한 지식공유자님 강의 - 실전! 스프링 부트와 JPA 활용2 - API 개발과 성능 최적화
728x90
Comments