엔티티를 DTO 로 변환했던 API 가 성능이 안나와서 페치 조인으로 최적화를 해보겠습니다.

주문 조회 V3: 엔티티를 DTO로 변환 - 페치 조인 최적화

@RestController
@RequiredArgsConstructor
public class OrderApiController {

    private final OrderRepository orderRepository;

    @GetMapping("/api/v3/orders")
    public List<OrderDto> ordersV3() {
        List<Order> orders = orderRepository.findAllWithItem();
        return orders.stream()
                .map(OrderDto::new)
                .collect(Collectors.toList());
    }
    
    @Data
    static class OrderDto {
        private Long orderId;
        private String name;
        private LocalDateTime orderDate;
        private OrderStatus orderStatus;
        private Address address;
        private List<OrderItemDto> orderItems;

        public OrderDto(Order order) {
            orderId = order.getId();
            name = order.getMember().getName();
            orderDate = order.getOrderDate();
            orderStatus = order.getStatus();
            address = order.getDelivery().getAddress();
            orderItems = order.getOrderItems().stream()
                    .map(OrderItemDto::new)
                    .collect(Collectors.toList());
        }
    }

    @Data
    static class OrderItemDto {
        private String itemName;
        private int orderPrice;
        private int count;

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

}

@Repository
@RequiredArgsConstructor
public class OrderRepository {

    private final EntityManager em;
    
    public List<Order> findAllWithItem() {
        return em.createQuery(
                        "select o from Order o" +
                        " join fetch o.member m" +
                        " join fetch o.delivery d" +
                        " join fetch o.orderItems oi" +
                        " join fetch oi.item i", Order.class)
                .getResultList();
    }

}

<aside> ❗ 참고로 지금은 jpql을 사용한다고 문자열을 직접 써야해서 답답할 수 있는데요, 실무에서 QueryDSL 을 사용하게 되면 정말 쉽게 해결할 수 있습니다.

참고

</aside>

문제 - 데이터 뻥튀기

성능을 위해 페치 조인을 써서 쿼리를 작성했는데요, 문제가 발생합니다.

우리가 원하는 데이터는 order 데이터 2개입니다. 그리고 order 하위에 객체 그래프로 채워진 각각의 데이터들이죠.

그런데 위 코드를 실행하면 데이터가 이상하게 나옵니다. 데이터가 중복되어 나오고 있습니다. orderId 4번이 2번 나오고 orderId 11번이 2번 나오고 있습니다.

[
    {
        "orderId": 4,
        "name": "userA",
        "orderDate": "2024-04-16T15:33:57.150891",
        "orderStatus": "ORDER",
        "address": {
            "city": "서울",
            "street": "1",
            "zipcode": "1111"
        },
        "orderItems": [
            {
                "itemName": "JPA1 BOOK",
                "orderPrice": 10000,
                "count": 1
            },
            {
                "itemName": "JPA2 BOOK",
                "orderPrice": 20000,
                "count": 2
            }
        ]
    },
    {
        "orderId": 4,
        "name": "userA",
        "orderDate": "2024-04-16T15:33:57.150891",
        "orderStatus": "ORDER",
        "address": {
            "city": "서울",
            "street": "1",
            "zipcode": "1111"
        },
        "orderItems": [
            {
                "itemName": "JPA1 BOOK",
                "orderPrice": 10000,
                "count": 1
            },
            {
                "itemName": "JPA2 BOOK",
                "orderPrice": 20000,
                "count": 2
            }
        ]
    },
    {
        "orderId": 11,
        "name": "userB",
        "orderDate": "2024-04-16T15:33:57.21018",
        "orderStatus": "ORDER",
        "address": {
            "city": "진주",
            "street": "2",
            "zipcode": "2222"
        },
        "orderItems": [
            {
                "itemName": "SPRING1 BOOK",
                "orderPrice": 20000,
                "count": 3
            },
            {
                "itemName": "SPRING2 BOOK",
                "orderPrice": 40000,
                "count": 4
            }
        ]
    },
    {
        "orderId": 11,
        "name": "userB",
        "orderDate": "2024-04-16T15:33:57.21018",
        "orderStatus": "ORDER",
        "address": {
            "city": "진주",
            "street": "2",
            "zipcode": "2222"
        },
        "orderItems": [
            {
                "itemName": "SPRING1 BOOK",
                "orderPrice": 20000,
                "count": 3
            },
            {
                "itemName": "SPRING2 BOOK",
                "orderPrice": 40000,
                "count": 4
            }
        ]
    }
]

왜 중복됐는지 알기 위해서는 DB 입장에서 생각을 해봅시다.

먼저 Order 와 Member 의 관계는 ManyToOne 입니다. 그리고 Order 와 Delivery 의 관계는 OneToOne 입니다. 그래서 조인을 해도 큰 문제가 없겠죠? (에서 보기도 했구요)

그런데 OrderItem 을 조인할때부터 문제가 생깁니다.