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

hugebird님의 프로필 이미지
hugebird

작성한 질문수

실전! Querydsl

쿼리 최적화 질문 (N+1문제)

해결된 질문

작성

·

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

hugebird님의 프로필 이미지
hugebird
질문자

앗 되게 간단하게 해결할 수 있는 문제였네요 ㅋㅋㅋㅋㅋㅋ

다시 한 번 복습의 중요성을 느꼈습니다 ㅎㅎ

답변 감사합니다!

1

김영한님의 프로필 이미지
김영한
지식공유자

안녕하세요. hugebird님

먼저 질문을 자세히 적어주셔서 감사합니다.

해답에 거의 근접하셨네요 ㅎㅎ

질문하신 OrderMenu의 Menu, OrderOption의 Option은 여전히 Lazy loading이 되었습니다.

-> 이 부분은 fetch join을 하지 않았기 때문에 당연히 지연로딩이 발생합니다.

menu, option도 함께 fetch join에 포함하면 원하는 최적화를 얻으실 수 있을거에요^^

(이렇게 뭔가 딱 실제 문제를 겪을 때 활용2편 복습하시면 바로 깨달음이 오실거에요 ㅎㅎ)

감사합니다.

hugebird님의 프로필 이미지
hugebird

작성한 질문수

질문하기