문제 상황
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
이유
- Querydsl 에서
on()절로 매핑하면 두 엔티티가 연관관계라는 것을 인식하지 못한다. - Querydsl 에서
on()절은 조건절을 위해 있는것이지 테이블을 엮기 위해 존재하는 것이 아니다.
위와 같이 생각한 이유는 HQL 공식 문서를 읽어 보고 알았다.
Querydsl 에서 SQL이 생성되기까지 다음의 변환을 거친다.
Querydsl → JPQL → HQL → SQL
HQL(Hibernate Query Language) 는 하이버네이트에서 사용하는 객체 지향적인 쿼리 언어로 HQL 구문 하나하나를 파싱해 SQL로 변환된다.
org.hibernate.hql.internal.ast.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 적용 안되는 이유
글 읽어주셔서 언제나 감사합니다. 좋은 피드백, 개선 피드백 너무나도 환영합니다.
'SearchDeveloper > SpringBoot' 카테고리의 다른 글
| [JPA] Multi Datasource 설정하기 (1) | 2022.11.06 |
|---|---|
| [JPA] Querydsl 시작하기 (0) | 2022.10.21 |
| Spring REST Docs 이해 (2) | 2022.09.24 |
| 스프링부트 jar 구성 및 실행 원리 (4) | 2022.09.08 |
| Spring Data Mongo save 시 DuplicateKeyException 나는 이유 및 해결 (0) | 2022.08.09 |