참치코더의 꿈 메모장

JPA / N+1 문제 및 해결 방법 본문

JPA

JPA / N+1 문제 및 해결 방법

참치깡 2026. 2. 3. 20:19
728x90

 

JPA를 쓰다 보면 성능이 갑자기 느려지는 구간이 있다.

그 대표적인 원인이 바로 N+1 문제다.

 

N+1 문제란?

 

- 연관된 데이터를 조회할 때 쿼리가 불필요하게 많이 실행되는 현상

 

게시글과 댓글이 있을때

 

Post (1) : (N) Comment

 

게시글 10개를 조회했을 때 실행되는 쿼리

 

1번: 게시글 10개 조회

10번: 각 게시글의 댓글 조회

 

-> 각각의 게시글에 작성되어 있는 댓글을 일일이 JPA Hibernate가 퍼오게 된다.

   이게 바로 N(게시글 수) + 1 = N+1 문제이다.

 

왜 발생할까?

 

- JPA의 지연 로딩(FetchType.LAZY) 때문이다.

 

1
2
3
4
5
6
7
8
9
10
11
12
@ManyToOne(fetch = FetchType.LAZY)
@OneToMany(mappedBy="post", fetch = GetchType.LAZY)
 
List<Post> posts = postRepository.findAll();
 
for (Post post : posts) {
    post.getComments().size(); // 여기서 댓글 쿼리 계속 발생
}
 
// 댓글을 실제로 사용하는 순간마다 쿼리가 실행된다.
 
 
cs

 

N+1이 위험한 이유

 

- DB 부하가 증가한다

- 응답 속도가 느려진다.

- 서버가 다운될 가능성이 높아진다.

 

 

해결방법

 

1. Fetch Join

 

1
2
3
@Query("SELECT p FROM Post p JOIN FETCH p.comments")
 
List<Post> findAllWithComments();
cs

 

 

- 이렇게 하면 쿼리가 1번만 실행되고 패치된 쿼리가 나간다.

- 하지만 1:N 관계에서 페이징이 불가능하다.

 

2. IN 조회 방식

 

- 페이징 +  컬렉션 조회 시 사용하는 방법이다. 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// 1. 게시글 먼저 페이징해서 가져옴
Page<Post> postPage = postRepository.findAll(pageable); // 게시글 10개 확보
 
// 2. 게시글 ID들을 모음
List<Long> postIds = postPage.stream()
                            .map(post::getId)
                            .tosList();
 
 
// 3. 댓글을 한 번에 조회
@Query("SELECT c FROM Comment c WHERE c.post.id IN :ids")
List<Comment> findByPostIdIn(List<Long> ids);
 
List<Comment> comments = commentRepository.findByPostIdIn(postIds);
 
// 4. 댓글을 게시글별로 묶음
Map<Long, List<Comment>> commentMap = 
    comments.stream().collect(
        Collectors.groupingBy(c -> c.getPost().getId())
);
 
// 5. DTO 만들면서 연결
List<PostDto> dtos = postPage.stream()
    .map(post -> new PostDto(
        post,
        commentMap.getOrDefault(post.getId(), List.of())
))
.toList();
cs

 

 

 - 만약 조회하려는 테이블 내부적으로 계속 1:N 관계 컬렉션이 존재한다면 사용하려는 깊이

   까지 위와 같은 IN 조회 방법으로 들어가서 꺼내야 한다.

 

- fetch 조인을 사용하면 가장 큰 문제는 페이징 처리가 되지 않는다는 것이고, 데이터가 기하급수적으로

  늘어날 가능성이 있어 네트워크상 문제가 커질 수 있기 때문이다.

 

- 아니면 쿼리 받는 데이터값 자체를 줄여야한다. (BatchSize 사용)

728x90
Comments