인프런 커뮤니티 질문&답변

이진혁님의 프로필 이미지

작성한 질문수

실전! 스프링 부트와 JPA 활용2 - API 개발과 성능 최적화

N + 1 쿼리 횟수

해결된 질문

작성

·

158

·

수정됨

2

안녕하세요 강의 너무 잘 듣고 있습니다.

"간단한 주문 조회 V2: 엔티티를 DTO로 변환" 강의를 듣던 중 궁금한 점이 생겨 질문 드립니다.

Order 엔티티를 SimpleOrderDto로 변환하는 과정에서 아래 조건으로 인해 샘플 데이터 2개 기준 총 5(1 + 2 + 2)번의 쿼리가 날아간다고 이해했습니다.

  • order -> member 지연 로딩 조회 N번

  • order -> delivery 지연 로딩 조회 N번

근데 저는 order -> delivery 쿼리 이후에 order를 찾는 쿼리가 한번 더 날아가는 것처럼 보입니다. 총 7번의 쿼리가 발생하는 것 같은데 무엇 때문인지 설명해주실 수 있을까요?

코드와 콘솔 로그는 아래 첨부했습니다. 감사합니다.

// Order 엔티티
    @Id
    @GeneratedValue
    @Column(name = "order_id")
    private Long id;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "member_id")
    private Member member;

    @OneToMany(mappedBy = "order", cascade = CascadeType.ALL)
    private List<OrderItem> orderItems = new ArrayList<>();

    @OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
    @JoinColumn(name = "delivery_id")
    private Delivery delivery;

    private LocalDateTime orderDate;

    @Enumerated(EnumType.STRING)
    private OrderStatus status; // 주문상태

// Delivery 엔티티

    @Id
    @GeneratedValue
    @Column(name = "delivery_id")
    private Long id;

    @JsonIgnore
    @OneToOne(mappedBy = "delivery", fetch = FetchType.LAZY)
    private Order order;

    @Embedded
    private Address address;

    @Enumerated(EnumType.STRING)
    private DeliveryStatus status; // READY, COMP

// OrderSimpleApiController

    @GetMapping("/api/v2/simple-orders")
    public List<SimpleOrderDTO> ordersV2() {
        List<Order> orders = orderRepository.findAll(new OrderSearch());

        return orders.stream()
                .map(SimpleOrderDTO::new)
                .toList();
    }

    @Data
    static class SimpleOrderDTO {

        private Long orderId;
        private String name;
        private LocalDateTime orderDate;
        private OrderStatus orderStatus;
        private Address address;

        public SimpleOrderDTO(Order order) {
            this.orderId = order.getId();
            this.name = order.getMember().getName(); // Lazy 초기화
            this.orderDate = order.getOrderDate();
            this.orderStatus = order.getStatus();
            this.address = order.getDelivery().getAddress(); // Lazy 초기화
        }

    }
2024-04-09T13:27:53.982+09:00 DEBUG 12424 --- [nio-8080-exec-2] org.hibernate.SQL                        : 
    select
        o1_0.order_id,
        o1_0.delivery_id,
        o1_0.member_id,
        o1_0.order_date,
        o1_0.status 
    from
        orders o1_0 
    join
        member m1_0 
            on m1_0.member_id=o1_0.member_id
2024-04-09T13:27:53.984+09:00  INFO 12424 --- [nio-8080-exec-2] p6spy                                    : 1712636873984|0|statement|connection 7|url jdbc:h2:tcp://localhost/~/jpashop_v2|select o1_0.order_id,o1_0.delivery_id,o1_0.member_id,o1_0.order_date,o1_0.status from orders o1_0 join member m1_0 on m1_0.member_id=o1_0.member_id|select o1_0.order_id,o1_0.delivery_id,o1_0.member_id,o1_0.order_date,o1_0.status from orders o1_0 join member m1_0 on m1_0.member_id=o1_0.member_id
2024-04-09T13:27:54.005+09:00 DEBUG 12424 --- [nio-8080-exec-2] org.hibernate.SQL                        : 
    select
        m1_0.member_id,
        m1_0.city,
        m1_0.street,
        m1_0.zipcode,
        m1_0.name 
    from
        member m1_0 
    where
        m1_0.member_id=?
2024-04-09T13:27:54.007+09:00  INFO 12424 --- [nio-8080-exec-2] p6spy                                    : 1712636874007|0|statement|connection 7|url jdbc:h2:tcp://localhost/~/jpashop_v2|select m1_0.member_id,m1_0.city,m1_0.street,m1_0.zipcode,m1_0.name from member m1_0 where m1_0.member_id=?|select m1_0.member_id,m1_0.city,m1_0.street,m1_0.zipcode,m1_0.name from member m1_0 where m1_0.member_id=1
2024-04-09T13:27:54.012+09:00 DEBUG 12424 --- [nio-8080-exec-2] org.hibernate.SQL                        : 
    select
        d1_0.delivery_id,
        d1_0.city,
        d1_0.street,
        d1_0.zipcode,
        d1_0.status 
    from
        delivery d1_0 
    where
        d1_0.delivery_id=?
2024-04-09T13:27:54.013+09:00  INFO 12424 --- [nio-8080-exec-2] p6spy                                    : 1712636874013|0|statement|connection 7|url jdbc:h2:tcp://localhost/~/jpashop_v2|select d1_0.delivery_id,d1_0.city,d1_0.street,d1_0.zipcode,d1_0.status from delivery d1_0 where d1_0.delivery_id=?|select d1_0.delivery_id,d1_0.city,d1_0.street,d1_0.zipcode,d1_0.status from delivery d1_0 where d1_0.delivery_id=1
2024-04-09T13:27:54.016+09:00 DEBUG 12424 --- [nio-8080-exec-2] org.hibernate.SQL                        : 
    select
        o1_0.order_id,
        o1_0.delivery_id,
        o1_0.member_id,
        o1_0.order_date,
        o1_0.status 
    from
        orders o1_0 
    where
        o1_0.delivery_id=?
2024-04-09T13:27:54.016+09:00  INFO 12424 --- [nio-8080-exec-2] p6spy                                    : 1712636874016|0|statement|connection 7|url jdbc:h2:tcp://localhost/~/jpashop_v2|select o1_0.order_id,o1_0.delivery_id,o1_0.member_id,o1_0.order_date,o1_0.status from orders o1_0 where o1_0.delivery_id=?|select o1_0.order_id,o1_0.delivery_id,o1_0.member_id,o1_0.order_date,o1_0.status from orders o1_0 where o1_0.delivery_id=1
2024-04-09T13:27:54.018+09:00 DEBUG 12424 --- [nio-8080-exec-2] org.hibernate.SQL                        : 
    select
        m1_0.member_id,
        m1_0.city,
        m1_0.street,
        m1_0.zipcode,
        m1_0.name 
    from
        member m1_0 
    where
        m1_0.member_id=?
2024-04-09T13:27:54.019+09:00  INFO 12424 --- [nio-8080-exec-2] p6spy                                    : 1712636874019|0|statement|connection 7|url jdbc:h2:tcp://localhost/~/jpashop_v2|select m1_0.member_id,m1_0.city,m1_0.street,m1_0.zipcode,m1_0.name from member m1_0 where m1_0.member_id=?|select m1_0.member_id,m1_0.city,m1_0.street,m1_0.zipcode,m1_0.name from member m1_0 where m1_0.member_id=2
2024-04-09T13:27:54.020+09:00 DEBUG 12424 --- [nio-8080-exec-2] org.hibernate.SQL                        : 
    select
        d1_0.delivery_id,
        d1_0.city,
        d1_0.street,
        d1_0.zipcode,
        d1_0.status 
    from
        delivery d1_0 
    where
        d1_0.delivery_id=?
2024-04-09T13:27:54.020+09:00  INFO 12424 --- [nio-8080-exec-2] p6spy                                    : 1712636874020|0|statement|connection 7|url jdbc:h2:tcp://localhost/~/jpashop_v2|select d1_0.delivery_id,d1_0.city,d1_0.street,d1_0.zipcode,d1_0.status from delivery d1_0 where d1_0.delivery_id=?|select d1_0.delivery_id,d1_0.city,d1_0.street,d1_0.zipcode,d1_0.status from delivery d1_0 where d1_0.delivery_id=2
2024-04-09T13:27:54.021+09:00 DEBUG 12424 --- [nio-8080-exec-2] org.hibernate.SQL                        : 
    select
        o1_0.order_id,
        o1_0.delivery_id,
        o1_0.member_id,
        o1_0.order_date,
        o1_0.status 
    from
        orders o1_0 
    where
        o1_0.delivery_id=?
2024-04-09T13:27:54.021+09:00  INFO 12424 --- [nio-8080-exec-2] p6spy                                    : 1712636874021|0|statement|connection 7|url jdbc:h2:tcp://localhost/~/jpashop_v2|select o1_0.order_id,o1_0.delivery_id,o1_0.member_id,o1_0.order_date,o1_0.status from orders o1_0 where o1_0.delivery_id=?|select o1_0.order_id,o1_0.delivery_id,o1_0.member_id,o1_0.order_date,o1_0.status from orders o1_0 where o1_0.delivery_id=2

답변 1

1

안녕하세요, 인프런 AI 인턴입니다.

이진혁님의 질문은 “간단한 주문 조회 V2: 엔티티를 DTO로 변환” 과정에서 예상보다 많은 쿼리가 발생하는 문제에 관한 것으로 보입니다. 이와 유사한 상황을 겪은 다른 학습자의 질문과 그에 대한 답변을 찾았습니다. 이 정보가 이진혁님의 질문에 참고가 될 수 있을 것 같습니다.

아래의 링크에서는 강의에서 기대한 쿼리 수와 실제 발생한 쿼리 수가 다른 경우에 대한 설명과 해결 방법에 대해 논의하고 있습니다.
- 쿼리가 강의보다 더 나오고 있습니다.

강의에서 기대하는 쿼리 발생 패턴과 실제 애플리케이션에서 발생하는 쿼리의 차이는 여러 요인에 의해 발생할 수 있습니다. 가장 흔한 원인 중 하나는 JPA의 지연로딩(Lazy Loading)과 즉시로딩(Eager Loading) 설정의 차이, 엔티티 간 연관관계 설정의 차이, 혹은 Hibernate 등 JPA 구현체의 버전 차이 등에 의한 것입니다.

가장 일반적인 경우, 이런 쿼리 발생 차이는 엔티티 간의 연관 관계 설정과 지연 로딩의 사용 여부에 따라 달라집니다. 특히 @OneToOne 또는 @ManyToOne 관계에서는 기본적으로 즉시 로딩(Eager Loading)이 설정되어 있어 예상치 못한 추가 쿼리가 발생할 수 있습니다. 해당 설정을 지연 로딩(Lazy Loading)으로 변경하거나, JPQL Fetch Join, Entity Graph 등을 활용하여 보다 효율적인 쿼리 실행이 가능합니다.

이진혁님께서 경험하고 있는 문제도 이와 유사한 사유에서 발생할 가능성이 높습니다. 이를 해결하기 위해서는 연관 관계 설정 및 쿼리 실행 방식을 확인하고 필요에 따라 수정해보시는 것을 권장드립니다.

저는 질문자님의 이해를 돕고자 지식공유자님의 기존 답변을 참고하여 유사한 답변 링크를 추천해드리고 있습니다.
현재 베타 기능으로 답변이 만족스럽지 않을 수 있는 점 양해 부탁드립니다. 🙏
추가적으로 궁금한 점이 있으시면, 이어서 질문해 주세요. 곧 지식공유자께서 답변해 주실 것입니다.

이진혁님의 프로필 이미지
이진혁
질문자

비슷한 질문을 못 찾았는데 역시 같은 질문을 하신 분들이 계셨네요...

덕분에 참고하여 해결된 것 같습니다😄