OneToMany 연관관계 조회 & 컬렉션 조회 V1 & V2

4810 단어 SpringbootJPAJPA

OneToMany 연관관계에서는 속성들이 컬렉션(List 등)이므로 이를 고려해서 연관관계 조회를 해야 합니다

V1, V2 모두 최적의 방법이 아니기 때문에, 두 방법을 사용하는 것을 추천하지 않습니다. 해당 포스팅은 이런한 방법이 있으며, 어떠한 문제점이 발생하며, 문제점들의 해결방안을 제시하기 위함 입니다.

V1: 엔티티

	@GetMapping("api/v1/collection-orders")
    public List<Order> ordersV1() {
        List<Order> all = orderRepository.findAllByCriteria(new OrderSearch());
        for (Order order : all) {
            //영속성 컨텍스트에 추가해주기
            order.getId();
            order.getMember().getUsername(); //Lazy 강제 초기화
            order.getOrderDate();
            order.getDelivery().getAddress(); //Lazy 강제 초기화
            List<OrderItem> orderItems = order.getOrderItems();
            orderItems.stream()
                    .forEach(o -> o.getItem().getName()); //Lazy 강제 초기화
        }

        return all;
    }

ManyToOne, OneToOne일때와 똑같은 문제 발생과 해결 방법도 비슷합니다. 여전히 절대 실무에서는 사용하면 안되는 방법입니다.

V2: DTO

API

	@GetMapping("api/v2/collection-orders")
    private Result ordersV2() {
        List<Order> orders = orderRepository.findAllByCriteria(new OrderSearch());
        List<OrderCollectionDTO> orderDTOS = orders.stream()
                .map(o -> new OrderCollectionDTO(o))
                .collect(Collectors.toList());

        return new Result(orderDTOS);
        /**
         * 지연 로딩 때문에 최악의 경우 총 1+5N의 쿼리문이 처리됩니다:
         * member , address 2N번(order 조회 수 만큼)
         * orderItem N번(order 조회 수 만큼)
         * item 2N번(orderItem 조회 수 만큼)(order.getItem().getName())
         */
    }

DTO

 @Data
    static class OrderCollectionDTO {
        private Long orderId;
        private String username; //주문자 이름
        private LocalDateTime orderDate;
        private OrderStatus orderStatus;
        private Address address; //배송지
        private List<OrderItemDTO> orderItems;
        /**
         * Response를 반환 할때 엔티티 반환 X
         * 그 뜻은, DTO에서도 엔티티 반환 X
         * 그러므로, OrderItem도 DTO로 변환해서 반환
         * 또한, OrderItem도 DTO로 변환하면 클라이언트가 원하는 값만 보낼 수 있음
         */

        public OrderCollectionDTO(Order order) {
            this.orderId = order.getId();
            this.username = order.getMember().getUsername();
            this.orderDate = order.getOrderDate();
            this.orderStatus = order.getStatus();
            this.address = order.getDelivery().getAddress();
            this.orderItems = order.getOrderItems().stream()
                    .map((o) -> new OrderItemDTO(o))
                    .collect(Collectors.toList());
        }
    }

    @Data
    static class OrderItemDTO {

        private String itemName;
        private int orderPrice;
        private int count;

        public OrderItemDTO(OrderItem orderItem) {
            this.itemName = orderItem.getItem().getName();
            this.orderPrice = orderItem.getOrderPrice();
            this.count = orderItem.getCount();
        }
    }

    @Data
    @AllArgsConstructor
    static class Result<T>{
        public T data;
    }

ManyToOne, OneToOne일때와 비슷합니다. 하지만, 추가로 고려해야 될 상황이 있습니다. DTO로 반환한다는 뜻은 클라이언트에 절대로 엔티티 자체를 보내지 않아햐 합니다. 그러므로, orderItems를 반환 할때도, List을 그대로 반환하는 것이 아니라, OrderItemDTO라는 새로운 DTO를 추가로 생성해서, OrderItem -> OrderItemDTO로 변환 후, List를 반환해야 합니다.

V2의 경우 여전히 1+N의 문제가 발생하는데, OrderItem -> OrderItemDTO로 변환하는 과정에서 추가로 쿼리문을 실행하게 됩니다. 그렇기 때문에, ManyToOne, OneToOne일때는 1+2N 이였지만, OneToMany에서는 1+5N의 쿼리가 발생하게 됩니다. 이러한 문제점 해결 및 최적화는 V3에서 다루도록 하겠습니다.

좋은 웹페이지 즐겨찾기