본문 바로가기

Trouble Shooting/Data API

QueryDSL - 3중 Join N+1 해결

 

 

보통 두개의 테이블에서 N+1 문제 발생시 Left Join을 통해서 해결을 한다.

API 제작 중에 3개의 테이블을 Left Join해서 N+1문제를 해결하고자 하였으나 쿼리문을 3개 발생시켜 이를 해결해서 쿼리문의 양을 줄이고 DB 서버에 부하를 줄이고자 하였다.

 

 

 

API

API를 간단하게 설명하면 사용자가 음식점에서 예약을 하면 예약이 DB에 영속화 된다. 이 후 정보 요청시 예약만한 경우에는 예약 정보만을 출력하지만 사용자가 해당 예약정보에 맞는 주문까지 하고 정보 요청시 총 3가지의 정보가 들어가게 된다.

 

 

 

기존 코드

해당 코드처럼 작성시 총 3개의 쿼리문이 발생한다 N+1문제는 발생하지 않지만 두개의 left join이 제대로 처리되지 않는다. 예를 들어, 1번 테이블과 2번테이블은 1대1, 2번테이블과 3번테이블은 1대N일 때, 2번과 3번에서 N+1이 발생하지는 않지만 하나하나 쿼리문을 불러 총 3번의 쿼리문을 발생시킨다.

StoreReservationEntity result = queryFactory
        .select(storeReservationEntity)
        .from(storeReservationEntity)
        .leftJoin(storeReservationEntity.child, storeOrdersEntity)
        .leftJoin(storeOrdersEntity.childSet, storeOrdersMapEntity)
        .where(storeReservationEntity.storeName.eq(storeName)
            .and(storeReservationEntity.userId.eq(userId)))
        .fetchFirst();

 

 

쿼리문 

Hibernate: 
    select
        s1_0.reservation_id,
        s1_0.created_at,
        s1_0.date,
        s1_0.store_name,
        s1_0.store_table,
        s1_0.time,
        s1_0.updated_at,
        s1_0.user_id 
    from
        reservation s1_0 
    left join
        store_orders c1_0 
            on s1_0.reservation_id=c1_0.reservation_id 
    left join
        store_orders_map c2_0 
            on c1_0.orders_id=c2_0.orders_id 
    where
        s1_0.store_name=? 
        and s1_0.user_id=? limit ?
Hibernate: 
    select
        s1_0.orders_id,
        s1_0.reservation_id 
    from
        store_orders s1_0 
    where
        s1_0.reservation_id=?
Hibernate: 
    select
        c1_0.orders_id,
        c1_0.order_id,
        c1_0.food_count,
        c1_0.food_name 
    from
        store_orders_map c1_0 
    where
        c1_0.orders_id=?

 

 

 

 

 

변경 코드

- fetchJoin() 메서드를 사용해서 해결하였다. 이는 3개 이상의 조인에서 사용가능하며 3개 이상의 테이블이 하나의 쿼리문으로 Lazy Loading을 피하게 도와준다.

StoreReservationEntity result = queryFactory
        .select(storeReservationEntity)
        .from(storeReservationEntity)
        .leftJoin(storeReservationEntity.child, storeOrdersEntity).fetchJoin()
        .leftJoin(storeOrdersEntity.childSet, storeOrdersMapEntity).fetchJoin()
        .where(storeReservationEntity.storeName.eq(storeName)
            .and(storeReservationEntity.userId.eq(userId)))
        .fetchFirst();

 

쿼리문

Hibernate: 
    select
        s1_0.reservation_id,
        c1_0.orders_id,
        c2_0.orders_id,
        c2_0.order_id,
        c2_0.food_count,
        c2_0.food_name,
        s1_0.created_at,
        s1_0.date,
        s1_0.store_name,
        s1_0.store_table,
        s1_0.time,
        s1_0.updated_at,
        s1_0.user_id 
    from
        reservation s1_0 
    left join
        store_orders c1_0 
            on s1_0.reservation_id=c1_0.reservation_id 
    left join
        store_orders_map c2_0 
            on c1_0.orders_id=c2_0.orders_id 
    where
        s1_0.store_name=? 
        and s1_0.user_id=?

 

'Trouble Shooting > Data API' 카테고리의 다른 글

Query DSL, Entity대신 DTO로 받기  (0) 2023.06.08
Querydsl 적용과 고찰  (0) 2023.06.05
SELECT + JOIN 개선에 대한 고찰  (0) 2023.05.15