JPA/JPA 활용

컬렉션 조회 최적화 1 - 페치 조인을 안 했을 시

chanhee01 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을 이용해서 최적화를 진행해야 한다.