Notice
Recent Posts
Recent Comments
Link
250x250
| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 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 | 29 | 30 |
Tags
- 자바스크립트
- 프런트엔드
- JavaScript
- 쿼리
- 디자인 패턴
- java
- 코드 테스트
- Next.js
- 알고리즘
- 프로그래머스
- 오라클
- 프론트엔드
- oracle
- BACK-END
- 서버
- 스프링
- 데이터베이스
- 자바
- jpa
- SQL
- web
- spring
- MySQL
- node.js
- jsp
- 코드테스트
- 미니정리
- 백엔드
- 정리
- 스프링부트
Archives
- Today
- Total
참치코더의 꿈 메모장
JPA / N+1 문제 및 해결 방법 본문
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번만 실행되고 패치된 쿼리가 나간다.
- 하지만 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
'JPA' 카테고리의 다른 글
| JPA / Named Query, 벌크 연산 기본 정리 (0) | 2025.11.10 |
|---|---|
| JPA / 다형성 쿼리, 엔티티 직접 사용 정리 (0) | 2025.11.07 |
| JPA / 경로 탐색, 패치 조인 미니 정리 (0) | 2025.11.05 |
| JPA / JPA 프로젝션, 페이징 API, JOIN 정리 (0) | 2025.10.29 |
| JPA / 값 타입 컬렉션(Value Type Collection) 정리 (0) | 2025.10.27 |
Comments