N+1 문제
는 데이터베이스 성능을 저하시키는 대표적인 문제입니다. 특히 연관된 엔티티를 조회할 때 발생하며, 한 번의 기본 쿼리(N) 이후, 연관 엔티티를 조회하기 위한 추가 쿼리(+1)가 반복적으로 발생합니다.
Comment
엔티티 목록을 조회하면서 각 댓글 작성자의 정보를 가져와야 한다고 가정해 보겠습니다.단순히 JOIN
만 사용하면 Comment
와 연관된 User
데이터가 지연 로딩(Lazy Loading
) 방식으로 개별 조회되면서 N+1 문제가 발생합니다.
@Query("SELECT c FROM Comment c JOIN c.user WHERE c.todo.id = :todoId")
List<Comment> findByTodoIdWithUser(@Param("todoId") Long todoId);
예를 들어, 댓글이 3개 있을 때 다음과 같은 쿼리들이 실행됩니다:
-- 1. 첫 번째 쿼리: 댓글(Comment) 목록 조회
SELECT c1_0.id, c1_0.contents, c1_0.todo_id, c1_0.user_id
FROM comments c1_0
WHERE c1_0.todo_id = :todoId;
-- 2. 각 댓글의 작성자(User)를 개별 조회 (총 3회)
SELECT u1_0.id, u1_0.email, u1_0.nickname
FROM users u1_0
WHERE u1_0.id = ?;
SELECT u1_0.id, u1_0.email, u1_0.nickname
FROM users u1_0
WHERE u1_0.id = ?;
SELECT u1_0.id, u1_0.email, u1_0.nickname
FROM users u1_0
WHERE u1_0.id = ?;
이렇게 각 댓글마다 User
를 조회하는 추가 쿼리가 발생하여 성능이 저하됩니다.
JOIN FETCH
사용하기JOIN FETCH
는 연관된 엔티티를 즉시 로딩하여 한 번의 쿼리로 모든 데이터를 조회합니다. 이를 통해 N+1 문제를 해결할 수 있습니다.
JOIN FETCH
사용@Query("SELECT c FROM Comment c JOIN FETCH c.user WHERE c.todo.id = :todoId")
List<Comment> findByTodoIdWithUser(@Param("todoId") Long todoId);
JOIN FETCH
를 사용하여 댓글 3개를 조회할 때는 다음과 같이 단일 쿼리로 모든 데이터가 조회됩니다:
-- 단일 쿼리로 Comment와 User를 모두 조회 (N+1 문제 해결)
SELECT c1_0.id, c1_0.contents, c1_0.todo_id, c1_0.user_id,
u1_0.id, u1_0.email, u1_0.nickname
FROM comments c1_0
JOIN users u1_0 ON c1_0.user_id = u1_0.id
WHERE c1_0.todo_id = :todoId;
JOIN FETCH
를 통해 추가적인 쿼리 발생 없이 필요한 모든 데이터를 한 번에 가져오므로 성능이 크게 개선됩니다.
JOIN FETCH
를 사용하면 데이터베이스 부하를 줄이고, N+1 문제를 해결하여 성능을 최적화할 수 있습니다.