ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 컬렉션 조회 최적화 1 - 페치 조인을 안 했을 시
    JPA/JPA 활용 2023. 8. 10. 15:57

    @OneToMany에서의 최적화를 하는 방법은 엔티티 조회 최적화보다 조금 더 까다롭다.

     

    여기서도 이전 방식처럼 성능이 안 나오는 것부터 점진적으로 발전해나가면서 할 예정이다.

     

     

    1. 엔티티를 그대로 노출

    @GetMapping("/api/v1/orders")
    public List<Order> ordersV1() {
        List<Order> all = orderRepository.findAllByString(new OrderSearch());
        for (Order order : all) {
            order.getMember().getName();
            order.getDelivery().getAddress();
    
            List<OrderItem> orderItems = order.getOrderItems();
            orderItems.stream().forEach(o -> o.getItem().getName());
        }
        return all;
    }

    API를 만들 때 엔티티를 직접 노출하는 것은 좋지 않다고 여러번 얘기했다. 엔티티를 직접 노출하는 것은 피해야 한다.

     

     

     

     

    2. DTO로 변환해서 반환하기

    @GetMapping("/api/v2/orders")
    public List<OrderDto> ordersV2() {
        List<Order> orders = orderRepository.findAllByString(new OrderSearch());
        List<OrderDto> collect = orders.stream()
                .map(o -> new OrderDto(o))
                .collect(Collectors.toList());
    
        return collect;
    }

    OrderDto를 스트림으로 돌릴 때 Order 목록을 루프로 돌리고 Order에서의 orderItems 목록을 또 루프로 돌린다. 그런데 이 때에는 SQL이 너무 많이 나온다는 단점이 있다. Order를 조회하는 SQL에서 order가 2번 조회된다. 그 이후에 member, delivery의 쿼리가 나가고 orderItems에서 item이 2개가 있기 때문에 item을 찾는 쿼리가 2번 나간다. 이 과정이 첫 번째 order의 SQL 호출 과정이고 다음 주문에서도 쿼리가 이만큼 나간다.

    쿼리가 너무 많이 나가기 때문에 성능상 문제가 되고 최적화를 더 신경써야만 한다.

     

     

     

    OrderDto

    @Getter
    static class OrderDto {
    
        private Long orderId;
        private String name;
        private LocalDateTime orderDate;
        private OrderStatus orderStatus;
        private Address address;
        private List<OrderItem> orderItems; // 여기에서 OrderItem이 들어간게 문제
    
        public OrderDto(Order order) {
            orderId = order.getId();
            name = order.getMember().getName();
            orderDate = order.getOrderDate();
            orderStatus = order.getStatus();
            address = order.getDelivery().getAddress();
            orderItems = order.getOrderItems();
        }
    }

    Dto를 만들 때에 주의점은 Dto에서도 엔티티를 사용하면 안된다는 것이다.

    별도의 OrderItemDto를 만들어서 위에 있는 OrderItem도 OrderItemDto로 변환해줘야 한다.

    @Getter
    static class OrderDto {
        ....
        private List<OrderItemDto> orderItems; // OrderItemDto로 보내기
    
        public OrderDto(Order order) {
            ....
            orderItems = order.getOrderItems().stream()
                        .map(orderItem -> new OrderItemDto(orderItem))
                        .collect(Collectors.toList()); // 여기도 Dto로 변환 필요
        }
    }
    
    @Getter
    static class OrderItemDto {
    
        private String itemName;
        private int orderPrice;
        private int count;
            
        public OrderItemDto(OrderItem orderItem) {
            itemName = orderItem.getItem().getName();
            orderPrice = orderItem.getItem().getPrice();
            count = orderItem.getCount();
        }
    }

     

     

    Dto를 만들었어도 쿼리가 너무 많이 나가기 때문에 저번에 공부했던 fetch join을 이용해서 최적화를 진행해야 한다.

Designed by Tistory.