쿼리 최적화 질문 (N+1문제)
안녕하세요! 강의 복습을 위해서 토이 프로젝트를 하는 중에 N+1 문제를 어떻게 해결해야 할 지 모르겠어서 질문 드립니다.
먼저, 제가 원하는 출력 결과는 아래와 같습니다.
====== 메뉴 정보 ======
id = 101
메뉴 = 후라이드
가격 = 14000
수량 = 2
====== 옵션 정보 ======
옵션 = 눈꽃 치즈볼 4개
가격 = 3500
수량 = 2
옵션 = 치즈볼 4개
가격 = 3000
수량 = 2
====== 메뉴 정보 ======
id = 22
메뉴 = 후라이드
가격 = 14000
수량 = 1
====== 옵션 정보 ======
1. 엔티티
[그림 1]. 엔티티 설계
※ OrderMenu에서 옵션을 선택하지 않으면
OrderMenu, OrderOption 조인 한 뒤에 OrderOption 조회 시 NullPointerException 발생해서
OrderMenu에 양방향 연관관계 추가
[그림 2]. Join 시 <null> 발생
2. 데이터
[그림 3]. 입력한 데이터
아래와 같은 테스트를 작성했을 때 [그림 4]과 같은 N+1 문제가 발생했습니다.
@Test
public void 주문메뉴_주문옵션조회() throws Exception {
Long orderId = 100L;
List<OrderMenu> fetch1 = queryFactory
.select(orderMenu)
.from(orderMenu)
.where(orderMenu.order.id.eq(orderId))
.fetch();
for (OrderMenu orderMenu : fetch1) {
System.out.println("====== 메뉴 정보 ======");
System.out.println("id = " + orderMenu.getId());
System.out.println("메뉴 = " + orderMenu.getMenu().getName());
System.out.println("가격 = " + orderMenu.getMenu().getPrice());
System.out.println("수량 = " + orderMenu.getQuantity());
List<OrderOption> orderOptionList = orderMenu.getOrderOption();
System.out.println("====== 옵션 정보 ======");
for (OrderOption orderOption : orderOptionList) {
System.out.println(" 옵션 = " + orderOption.getOption().getName());
System.out.println(" 가격 = " + orderOption.getOption().getPrice());
System.out.println(" 수량 = " + orderOption.getQuantity());
}
}
}
[그림 4] N + 1 문제 발생
위 문제를 해결하기 위해서 fetch join을 사용해서 OrderMenu와 OrderOption을 한 번에 불러왔지만
OrderMenu의 Menu, OrderOption의 Option은 여전히 Lazy loading이 되었습니다.
@Test
public void 주문메뉴_주문옵션조회() throws Exception {
Long orderId = 100L;
List<OrderMenu> fetch = queryFactory
.selectFrom(orderMenu)
.distinct()
.leftJoin(orderMenu.orderOption, orderOption).fetchJoin()
.where(orderMenu.order.id.eq(orderId))
.fetch();
for (OrderMenu orderMenu : fetch) {
System.out.println("====== 메뉴 정보 ======");
System.out.println("id = " + orderMenu.getId());
System.out.println("메뉴 = " + orderMenu.getMenu().getName());
System.out.println("가격 = " + orderMenu.getMenu().getPrice());
System.out.println("수량 = " + orderMenu.getQuantity());
List<OrderOption> orderOptionList = orderMenu.getOrderOption();
System.out.println("====== 옵션 정보 ======");
for (OrderOption orderOption : orderOptionList) {
System.out.println(" 옵션 = " + orderOption.getOption().getName());
System.out.println(" 가격 = " + orderOption.getOption().getPrice());
System.out.println(" 수량 = " + orderOption.getQuantity());
}
}
}
출력 내용
select
distinct ordermenu0_.order_menu_id as order_me1_7_0_,
orderoptio1_.order_option_id as order_op1_8_1_,
ordermenu0_.menu_id as menu_id3_7_0_,
ordermenu0_.order_id as order_id4_7_0_,
ordermenu0_.quantity as quantity2_7_0_,
orderoptio1_.option_id as option_i3_8_1_,
orderoptio1_.order_menu_id as order_me4_8_1_,
orderoptio1_.quantity as quantity2_8_1_,
orderoptio1_.order_menu_id as order_me4_8_0__,
orderoptio1_.order_option_id as order_op1_8_0__
from
order_menu ordermenu0_
left outer join
order_option orderoptio1_
on ordermenu0_.order_menu_id=orderoptio1_.order_menu_id
where
ordermenu0_.order_id=?
====== 메뉴 정보 ======
id = 101
2021-01-27 15:12:12.595 DEBUG 35340 --- [ main] org.hibernate.SQL :
select
menu0_.menu_id as menu_id1_3_0_,
menu0_.created_date as created_2_3_0_,
menu0_.last_modified_date as last_mod3_3_0_,
menu0_.menu_category_id as menu_cat8_3_0_,
menu0_.name as name4_3_0_,
menu0_.price as price5_3_0_,
menu0_.image_url as image_ur6_3_0_,
menu0_.share_url as share_ur7_3_0_,
menucatego1_.menu_category_id as menu_cat1_4_1_,
menucatego1_.name as name2_4_1_,
menucatego1_.restaurant_id as restaura3_4_1_
from
menu menu0_
left outer join
menu_category menucatego1_
on menu0_.menu_category_id=menucatego1_.menu_category_id
where
menu0_.menu_id=?
메뉴 = 후라이드
가격 = 14000
수량 = 2
====== 옵션 정보 ======
2021-01-27 15:12:12.604 DEBUG 35340 --- [ main] org.hibernate.SQL :
select
option0_.option_id as option_i1_6_0_,
option0_.name as name2_6_0_,
option0_.option_category_id as option_c4_6_0_,
option0_.price as price3_6_0_
from
options option0_
where
option0_.option_id=?
옵션 = 눈꽃 치즈볼 4개
가격 = 3500
수량 = 2
2021-01-27 15:12:12.609 DEBUG 35340 --- [ main] org.hibernate.SQL :
select
option0_.option_id as option_i1_6_0_,
option0_.name as name2_6_0_,
option0_.option_category_id as option_c4_6_0_,
option0_.price as price3_6_0_
from
options option0_
where
option0_.option_id=?
옵션 = 치즈볼 4개
가격 = 3000
수량 = 2
====== 메뉴 정보 ======
id = 22
메뉴 = 후라이드
가격 = 14000
수량 = 1
====== 옵션 정보 ======
OrderMenu와 OrderOption을 조회할 때, 연관된 Menu와 Option을 한 번에 조회하는 방법이 있을까요?
아니면 엔티티 설계를 변경해야 할까요?
답변 2
1
안녕하세요. hugebird님
먼저 질문을 자세히 적어주셔서 감사합니다.
해답에 거의 근접하셨네요 ㅎㅎ
질문하신 OrderMenu의 Menu, OrderOption의 Option은 여전히 Lazy loading이 되었습니다.
-> 이 부분은 fetch join을 하지 않았기 때문에 당연히 지연로딩이 발생합니다.
menu, option도 함께 fetch join에 포함하면 원하는 최적화를 얻으실 수 있을거에요^^
(이렇게 뭔가 딱 실제 문제를 겪을 때 활용2편 복습하시면 바로 깨달음이 오실거에요 ㅎㅎ)
감사합니다.
SpringBoot 4.X에서의 Querydsl 설정
0
77
2
querydsl 오픈소스에 대한 질문
0
69
1
예제에서의 카운트 쿼리에서 join문과 where문은 필요없지 않나요?
0
108
1
Querydsl 6.X버전에 대해서 어떻게 생각하시나요?
0
314
2
여러 테이블 조인하여 통계치를 구하고자 할 때 어떤 방법이 더 효율적일까요
1
68
1
fetchResults()는 더이상 권장되지 않는다는데 맞나요?
0
159
1
querydsl sum() 메서드 없어요.
0
157
2
build 디렉터리 생성
0
135
2
자바 ORM 표준 JPA 프로그래밍 - 기본편 듣고 바로 학습해도 괜찮을까요?
0
113
2
현재 Querydsl에서 from절 서브쿼리를 지원하나요?
0
89
1
오타 제보 드립니다.
0
70
2
벌크 연산과 flush, clear
0
76
1
Run As Intellij 로 변경시 Q타입 import 불가
0
87
1
QHello import하기 문제 발생
0
147
2
등록된 함수 보는법(H2Dialect) 질문
0
68
2
5.0부터 Querydsl은 향후 fetchCount() , fetchResult() 를 지원하지 않기로 결정했다고 하는데 이에 맞는 강의
1
195
2
[환경설정 PDF 부트 3.0이후 설명 질문] build.gradle에 compileQuerydsl을 정의하지 않은 상태에서 Gradle->Tasks->other->compileQuerydsl을 클릭하라고 하는 이유가 무엇인가요??
1
200
1
querydsl 설정 문제
0
222
2
quey dsl 설정부분
0
157
2
count 쿼리 관련 질문입니다!
0
75
1
stringtemplate를 이용하여 where절 검색 방법 질문 드립니다.
0
89
1
답변부탁드리겠습니다.
0
89
2
(OrderSpecifier)관련 내용 어디있을가요
0
64
1
중급문법 벌크연산에서
0
81
2





