🐞 JPA N+1 문제 - Fetch Join & EntityGraph
[문제 요약]
증상: 글 목록 페이지에서 글 조회 시 Category를 건별로 추가 조회 (N+1 문제 발생)
원인: JPA 쿼리에서 fetch join을 명시하지 않아 발생
해결: @EntityGraph(attributePaths = “category”)로 한 번에 조회
기존에는 글 목록을 조회 시 JpaRepository에서 아래와 같은 쿼리를 실행하였다.
java
@Query("""
SELECT p FROM Post p
WHERE p.status = :status
ORDER BY p.createdAt DESC, p.id DESC
""")
Slice<Post> findPublishedPosts(@Param("status") PostStatus status, Pageable pageable);
실제 실행된 쿼리는 아래와 같다.
plaintext
Hibernate:
select
p1_0.id,
p1_0.category_id,
p1_0.content,
-- 등등 필드들
from
post p1_0
where
p1_0.status=?
order by
p1_0.created_at desc,
p1_0.id desc
fetch
first ? rows only
Hibernate:
select
c1_0.id,
c1_0.created_at,
-- 등등 필드들
from
category c1_0
where
c1_0.id=?
Hibernate:
select
c1_0.id,
c1_0.created_at,
-- 등등 필드들
from
category c1_0
where
c1_0.id=?
-- 이하 카테고리 개수만큼 반복
JPA @ManyToOne의 기본 fetch 전략은 EAGER이지만 Hibernate가 SELECT로 가져와서 N+1 문제가 발생한 것으로 보였다.
해결 방법 1) JPQL fetch join
쿼리에서 명시적으로 조인 로딩을 지시한다.
java
@Query("""
SELECT p FROM Post p
LEFT JOIN FETCH p.category
WHERE p.status = :status
ORDER BY p.createdAt DESC, p.id DESC
""")
Slice<Post> findPublishedPosts(@Param("status") PostStatus status, Pageable pageable);
* category가 nullable이기 때문에 LEFT JOIN을 선택하였다.
해결 방법 2) EntityGraph
쿼리를 수정하지 않고 @EntityGraph를 사용한다.
java
@EntityGraph(attributePaths = { "category" })
@Query("""
SELECT p FROM Post p
WHERE p.status = :status
ORDER BY p.createdAt DESC, p.id DESC
""")
Slice<Post> findPublishedPosts(@Param("status") PostStatus status, Pageable pageable);
위 두 개의 방법 모두 아래와 같은 쿼리가 실행되었다.
plaintext
Hibernate:
select
p1_0.id,
c1_0.id,
c1_0.created_at,
-- 등등 필드들
from
post p1_0
left join
category c1_0
on c1_0.id=p1_0.category_id
where
p1_0.status=?
order by
p1_0.created_at desc,
p1_0.id desc
fetch join을 명시적으로 @Query 안에 작성하는 경우 조인 타입과 조건을 명확하게 제어할 수 있다는 점이 장점이지만 현시점에서는 글 목록 조회 쿼리가 복잡하지 않기 때문에 가독성이 좋은 @EntityGraph를 사용하기로 결정하였다.