본문 바로가기

SearchDeveloper/SpringBoot

[JPA] Querydsl 에서 Fetch Join 적용 안되는 이유

문제 상황

Fetch join 은 한 번의 쿼리로 JOIN 대상 테이블 데이터까지 한 번에 가져오고 영속성 컨텍스트에 넣어주기 때문에 n+1 문제를 해결하기 위한 하나의 방법이다.

그래서 fetch join 을 적용하면 실제로 그렇게 되는지 확인해보고 싶었다.

Querydsl

QBook book = QBook.book;
QPaper paper = QPaper.paper;

List<Book> bookList = jpaQueryFactory
                           .selectFrom(book)
                           .join(book).on(book.id.eq(paper.id)).fetchJoin()
                           .fetch();

생성된 SQL

SELECT      book.id
FROM        book 
INNER JOIN  paper  ON (book.id=paper.id)

SELECT   paper.id    
FROM     paper 
WHERE    paper.id=?

SELECT   paper.id    
FROM     paper 
WHERE    paper.id=?

...

..❓ n+1 문제가 발생했다.

나의 가설대로라면 첫 번째 쿼리 한 번만 실행하고 [paper] 테이블 컬럼까지 한꺼번에 조회해야하는데... 마치 Querydsl이 fetchJoin() 을 못 보고 지나친듯이 쿼리가 실행됐고 실제로 fetchJoin() 을 붙이나 안 붙이나 동일한 쿼리가 생성되었다.

해결

하루꼬박 헤매다 동료의 도움을 받았고 다음과 같이 변경하니 원하는대로 fetch join이 적용된 쿼리가 생성되었다.

.join(book).on(book.id.eq(paper.id)).fetchJoin()    →   .join(book.paper, paper)

Querydsl

   List<Book> bookList = jpaQueryFactory
                .selectFrom(book)
                .join(book.paper, paper)
                .fetch();

생성된 SQL

select
    book.id,
    paper.id
from
    book 
inner join
    paper on book.id=paper.id

이유

  1. Querydsl 에서 on() 절로 매핑하면 두 엔티티가 연관관계라는 것을 인식하지 못한다.
  2. Querydsl 에서 on() 절은 조건절을 위해 있는것이지 테이블을 엮기 위해 존재하는 것이 아니다.

위와 같이 생각한 이유는 HQL 공식 문서를 읽어 보고 알았다.

Querydsl 에서 SQL이 생성되기까지 다음의 변환을 거친다.

Querydsl → JPQL → HQL → SQL

HQL(Hibernate Query Language) 는 하이버네이트에서 사용하는 객체 지향적인 쿼리 언어로 HQL 구문 하나하나를 파싱해 SQL로 변환된다.

org.hibernate.hql.internal.ast.QueryTranslatorImpl.java

QueryTranslatorImpl.java

그럼 Querydsl 로부터 변환된 HQL을 확인해보자

<정상적으로 fetch join 적용된 HQL>

Querydsl

.join(book.paper, paper)

HQL

SELECT      book 
FROM        Book book   
INNER JOIN  fetch book.paper as paper

<fetch join 적용이 안된 HQL>

Querydsl

.join(book).on(book.id.eq(paper.id)).fetchJoin()

HQL

SELECT      book 
FROM        Book book   
INNER JOIN  fetch Paper paper with book.id = paper.id

HQL 공식문서에 따르면 fetch 란 다음과 같다.

💡 A "fetch" join allows associations or collections of values to be initialized along with their parent objects using a single select. This is particularly useful in the case of a collection. It effectively overrides the outer join and lazy declarations of the mapping file for associations and collections.

fetch join을 사용하면 한 번의 쿼리로 부모와 연관성있는 값을 초기화할 수 있다. (여기서 parent 는 문맥상 상속의 개념이 아닌 상위 엔티티에서 참조가능한 하위 엔티티와의 관계를 지칭하는 것같다.)

즉, book.paper 같이 paper의 부모가 book 이라는 연관성 정보를 줘야 하는데 fetch join 적용이 안된 쿼리에서는 두 엔티티의 연관성을 알 수 없는 것이다.

네❓ on() 절에서 연관성을 제공해준 거 아니냐고요?

with 키워드의 사용 의도

with 는 join 시 추가적인 조건이 필요할 때 쓰인다고 말한다. 즉, with 는 연관관계를 매핑하기 위해 사용되는 구문이 아니란 뜻이다. querydsl 의 on() 절은 HQL의 with 로 파싱되고 id 로 엮어달라고 명시해도 엔티티 간의 연관성으로 설정하는 것이 아닌 추가 조건절로 인식하여 연관 정보를 알지 못해 fetch join이 적용이 안 됐던 것이다.

 

레퍼런스

https://docs.jboss.org/hibernate/orm/3.3/reference/en/html/queryhql.html#queryhql-joins

 

[JPA] Querydsl 에서 Fetch Join 적용 안되는 이유

 

글 읽어주셔서 언제나 감사합니다. 좋은 피드백, 개선 피드백 너무나도 환영합니다.