해결된 질문
작성
·
325
0
안녕하세요! 강의 복습을 위해서 토이 프로젝트를 하는 중에 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
1
안녕하세요. hugebird님
먼저 질문을 자세히 적어주셔서 감사합니다.
해답에 거의 근접하셨네요 ㅎㅎ
질문하신 OrderMenu의 Menu, OrderOption의 Option은 여전히 Lazy loading이 되었습니다.
-> 이 부분은 fetch join을 하지 않았기 때문에 당연히 지연로딩이 발생합니다.
menu, option도 함께 fetch join에 포함하면 원하는 최적화를 얻으실 수 있을거에요^^
(이렇게 뭔가 딱 실제 문제를 겪을 때 활용2편 복습하시면 바로 깨달음이 오실거에요 ㅎㅎ)
감사합니다.