Java/JPA

[API 개발 고급] 지연 로딩과 조회 성능 최적화(V2) 2/4

Lea Hwang 2022. 5. 25. 18:27

간단한 주문 조회 V2: 엔티티를 DTO로 변환 (fetch join 사용 X)

 

결론 : 지연 로딩으로 쿼리 N번 호출

  • N + 1문제

해결 : fetch join 사용 → V3에서 설명

 

 

 

 

OrderSimpleApiController

package jpabook.jpashop.api;


/**
 * @XToOne 관계(ManyToOne, OneToOne에서의 성능최적화)
 * Order
 * Order -> Member
 * Order -> Delivery
 */
@RestController
@RequiredArgsConstructor
public class OrderSimpleApiController {

    private final OrderRepository orderRepository;

    @GetMapping("/api/v2/simple-orders")
    public List<SimpleOrderDto> ordersV2() {
        List<Order> orders = orderRepository.findAllByString(new OrderSearch());
        // [중요] orders 엔티티 그대로 반환하면 안되고 DTO로 변환 후 반환
        // ORDER 2개(N)
        // N+1문제 : 1 + 회원N + 배송N
        List<SimpleOrderDto> result = orders.stream()
                .map(o -> new SimpleOrderDto(o))
                .collect(Collectors.toList());
        return result;
    }

    @Data
    static class SimpleOrderDto {
        private Long orderId;
        private String name;
        private LocalDateTime orderDate;
        private OrderStatus orderStatus;
        private Address address; //배송지 주소

        //생성자
        public SimpleOrderDto(Order order) {
            orderId = order.getId();
            name = order.getMember().getName(); //LAZY 초기화
            orderDate = order.getOrderDate();
            orderStatus = order.getStatus();
            address = order.getDelivery().getAddress(); //LAZY 초기화
        }
    }

}

원래는 List로 반환하면 안 되고 Result로 감싸야하지만 

여기서는 그게 핵심이 아니므로 우선 List로 반환하였습니다.

 

  • 해당 내용 포스팅에 자세히 서술하였습니다.
    • MemberApiController의 static class Result<T> { private T data; } 부분

 

 

 

Postman 확인

 

이렇게 잘 호출되는 것 같지만 V1, V2의 공통적인 문제점이 있습니다.

지연 로딩으로 인한 데이터베이스 쿼리가 너무 많이 호출된다는 것입니다.

 

코드를 보면서 유추를 해보자면

Order, Member, Delivery 이렇게 3개만 조회 쿼리가 나갈 것 같지만

 

실제는

콘솔에서 확인할 수 있는 것처럼

 

첫 번째 주문 시

Order조회

Member조회

Delivery 조회

하고

 

두 번째 주문 시

Member조회

Delivery조회

이렇게 또 가져와서 총 5번 쿼리가 나갔습니다.

 

 

 

이것이 유명한(?)  N+1문제입니다!

쿼리가 총 1 + N + N번 실행된다. (v1과 쿼리수 결과는 같습니다.)
order 조회 1번(order 조회 결과 수가 N이 된다.)
order -> member 지연 로딩 조회 N 번
order -> delivery 지연 로딩 조회 N 번

[참고]
order의 결과가 4개면 최악의 경우(= 다른 유저가 주문 시) 1 + 4 + 4번 실행됩니다.
    지연 로딩은 영속성 컨텍스트에서 조회하므로, 이미 조회된 경우 쿼리를 생략합니다.
    따라서 만약 각기 다른 유저가 주문하면 N번이고, 만약 같은 유저가 주문했다면 1번으로 끝납니다.

 

 

 

 

 

 

V2에서는 엔티티를 DTO로 변환하는 일반적인 방법을 알아보았습니다.

다음 포스팅(V3)에서는 N+1문제를 페치 조인을 통해 해결해보겠습니다.