문제 상황
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 |