JPA · 2025-11-05 · 1분 읽기
JPA N+1 문제, 제대로 이해하고 해결하기
목차
- ›1. N+1이 뭔가
- ›2. 왜 발생하나
- ›3. Fetch Join으로 해결
- ›4. @EntityGraph
- ›5. Batch Size로 해결
- ›6. 어떤 걸 써야 하나
1. N+1이 뭔가
Post를 조회했는데 쿼리가 N+1개 나가는 현상이다.
// 이 코드 한 줄이
List<Post> posts = postRepository.findAll();
// 이런 쿼리를 만들어낸다
// SELECT * FROM post → 1번
// SELECT * FROM comment WHERE post_id = 1 → N번
// SELECT * FROM comment WHERE post_id = 2
// ...2. 왜 발생하나
기본 fetch 전략이 LAZY이기 때문이다. 연관 엔티티는 실제로 접근할 때 쿼리가 나간다.
@Entity
public class Post {
@OneToMany(mappedBy = "post", fetch = FetchType.LAZY)
private List<Comment> comments;
}EAGER로 바꿔도 해결이 안 된다. JPQL은 fetch 전략을 무시하고 별도 쿼리를 날린다.
3. Fetch Join으로 해결
@Query("SELECT p FROM Post p JOIN FETCH p.comments")
List<Post> findAllWithComments();쿼리 한 방에 해결된다.
SELECT p.*, c.*
FROM post p
INNER JOIN comment c ON c.post_id = p.id단, 컬렉션 Fetch Join은 페이징 불가하다. HibernateJpaDialect: HHH90003004 경고와 함께 인메모리 페이징이 일어난다.
4. @EntityGraph
@EntityGraph(attributePaths = {"comments", "tags"})
List<Post> findAll();Fetch Join보다 선언적으로 쓸 수 있다. 하지만 여러 컬렉션을 동시에 사용하면 MultipleBagFetchException이 발생한다.
5. Batch Size로 해결
컬렉션 페이징이 필요할 때 쓰는 방법이다.
@BatchSize(size = 100)
@OneToMany(mappedBy = "post")
private List<Comment> comments;또는 전역 설정:
spring:
jpa:
properties:
hibernate:
default_batch_fetch_size: 100N+1 대신 IN 쿼리로 묶어서 처리한다.
-- N+1 대신
SELECT * FROM comment WHERE post_id IN (1, 2, 3, ..., 100)6. 어떤 걸 써야 하나
| 상황 | 해결책 |
|---|---|
| 단건 조회, 컬렉션 없음 | Fetch Join |
| 목록 조회, 페이징 없음 | Fetch Join |
| 목록 조회, 페이징 있음 | BatchSize |
| 복잡한 조인 필요 | QueryDSL + DTO 프로젝션 |
'JPA ' 카테고리의 다른 글
- 이 카테고리에 다른 글이 없습니다.