N + 1 쿼리 횟수
안녕하세요 강의 너무 잘 듣고 있습니다.
"간단한 주문 조회 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 등을 활용하여 보다 효율적인 쿼리 실행이 가능합니다.
이진혁님께서 경험하고 있는 문제도 이와 유사한 사유에서 발생할 가능성이 높습니다. 이를 해결하기 위해서는 연관 관계 설정 및 쿼리 실행 방식을 확인하고 필요에 따라 수정해보시는 것을 권장드립니다.
저는 질문자님의 이해를 돕고자 지식공유자님의 기존 답변을 참고하여 유사한 답변 링크를 추천해드리고 있습니다.
현재 베타 기능으로 답변이 만족스럽지 않을 수 있는 점 양해 부탁드립니다. 🙏
추가적으로 궁금한 점이 있으시면, 이어서 질문해 주세요. 곧 지식공유자께서 답변해 주실 것입니다.
강의 관련 외 질문입니다.
0
66
2
SpringBoot4 + Hibernate7 모듈 등록 방법 공유
0
89
1
BeanCreationException
0
87
3
Update 후 UpdateMemberResponse 매핑할 때
0
47
1
트랜잭션을 사용 안 할 때 커넥션은 언제 가져오나요?
0
99
2
페이징 + 검색조건 관련해서 질문드립니다.
0
70
1
Query Dsl Q파일 질문입니다.
0
82
1
루트 쿼리라는것은
0
58
1
메서드를 분리하는 기준
0
63
1
findAllWithMemberDelivery 메서드 질문드립니다.
0
110
3
연관관계 매핑을 안 쓸 경우, 사용해야 하는 전략
0
84
2
fetch join과 영속화와 OSIV의 관계
0
85
2
Distinct 사용 전 결과에 대한 의문
0
113
2
레포지토리 계층에서의 트랜잭션에 대한 의문
0
57
1
영속성 컨텍스트 생명주기의 신기한 부분이 있습니다.
0
78
2
dto 필드 속 엔티티 여부
0
60
1
뷰템플릿 사용 시
0
76
2
Result 클래스 관련 질문
0
56
1
@PostConstruct 프록시 관련 질문드립니다
0
86
1
DTO 대신 Form 사용은 안되나요?
0
135
1
OSIV ON 상태일 때
0
96
1
fetch join VS fetch join 페이징 궁금증
0
180
2
양방향 연관관계 알아보는 법?
0
105
1
16강 17강 간단 정리 이게 맞을까요 ?
0
165
2





