일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 | 31 |
- doitandroid
- 카카오코테
- 혼공챌린지
- 정처기
- 인프런
- Kotlin
- 자료구조
- 코테
- 기술면접
- select
- 코틀린
- Til
- SQL
- java
- 스터디
- Android
- 프로그래머스
- 혼공단
- 혼공파
- 정보처리기사
- CS
- 티스토리챌린지
- join
- 안드로이드스튜디오
- 알고리즘
- 오블완
- groupby
- 자바
- 안드로이드
- MySQL
- Today
- Total
Welcome! Everything is fine.
[Spring] @EntityGraph로 N+1문제 해결하기 본문
다음 getTodos() 메서드에서 발생하고 있는 N+1 문제를 @EntityGraph를 사용해 해결해야 한다.
public Page<TodoResponse> getTodos(int page, int size) {
Pageable pageable = PageRequest.of(page - 1, size);
Page<Todo> todos = todoRepository.findAllByOrderByModifiedAtDesc(pageable);
return todos.map(todo -> new TodoResponse(
todo.getId(),
todo.getTitle(),
todo.getContents(),
todo.getWeather(),
new UserResponse(todo.getUser().getId(), todo.getUser().getEmail()),
todo.getCreatedAt(),
todo.getModifiedAt()
));
}
위 코드에서 할일을 모두 불러온 후, 그 아래 유저를 또 다시 불러오는 과정에서 N+1 문제가 발생할 가능성이 높아보인다.
Todo 엔티티의 일부를 살펴보면 User와 @ManyToOne 다대일 연관관계를 맺고 있고, 지연 로딩(FetchType.LAZY)으로 설정되어 있다. 지연 로딩으로 설정하면 Todo를 조회할 때 User를 바로 가져오는 것이 아니라 프록시 객체로 있다가 실제 사용될 때 SELECT 쿼리가 실행되는 것이다.
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "user_id", nullable = false)
private User user;
먼저 findAllByOrderByModifiedAtDesc() 메서드를 통해 다음과 같이 모든 todo를 불러오고 있다.
Page<Todo> todos = todoRepository.findAllByOrderByModifiedAtDesc(pageable);
이때 다음과 같은 쿼리가 실행될 것이다.
SELECT * FROM todo ORDER BY modified_at DESC LIMIT ?, ?
그리고 다음 코드에서 todo.getUser()를 하면 User에 대한 SELECT 추가 쿼리가 실행된다. 즉 todo가 100개라면 위에서 1개의 쿼리를 실행하고, 아래에서 100개의 쿼리가 또 발생하게 되는 것이다.
todos.map(todo -> new TodoResponse(
todo.getId(),
todo.getTitle(),
todo.getContents(),
todo.getWeather(),
new UserResponse(todo.getUser().getId(), todo.getUser().getEmail()), // N+1 문제 발생 가능
todo.getCreatedAt(),
todo.getModifiedAt()
));
Repository에서는 이미 다음과 같이 fetch join을 사용해 N+1 문제를 해결했다.
@Query("SELECT t FROM Todo t LEFT JOIN FETCH t.user u ORDER BY t.modifiedAt DESC")
Page<Todo> findAllByOrderByModifiedAtDesc(Pageable pageable);
여기서 @EntityGraph라는 어노테이션을 활용해 해결해보자. @EntityGraph(attributePaths = {"user"})와 같이 적어주면 User도 함께 조회하도록 설정할 수 있다.
@EntityGraph(attributePaths = {"user"})
Page<Todo> findAllByOrderByModifiedAtDesc(Pageable pageable);
+) N+1 문제 눈으로 확인하기👀
오늘 팀원분께서 N+1 문제를 해결하지 않았을 때의 결과와 해결했을 때의 결과를 직접 보여주셨다. 그래서 나도 따라서 테스트 해보니 훨씬 이해가 잘됐다. 미리 유저 2명을 생성하고, 일정도 각 유저마다 1개씩 만들어 테스트 했더니 다음과 같은 결과가 나왔다.
N+1 문제를 해결하기 전에는 일정을 조회하고, 각 유저마다 한 번씩 더 조회하고 있다. 유저의 수(2명)만큼 쿼리가 실행된 것이다. N+1 문제를 해결한 후에는 유저를 추가로 조회하는 것 없이 쿼리가 한 번만 실행되는 것을 볼 수 있다.
'TIL' 카테고리의 다른 글
[Spring] JWT 시크릿 키 설정 및 문제 해결 / -hex 64 vs -base64 32 차이 (0) | 2025.02.27 |
---|---|
[Spring] Spring에서 세션을 다루는 3가지 방법, 뭐가 제일 좋을까? (0) | 2025.02.22 |
[Spring] Converter와 Fomatter 사용하기 (0) | 2025.02.21 |
[Git] 로컬 브랜치에서 pull 할 때 에러 발생 - stash로 임시 커밋하기 (0) | 2025.02.18 |
[Spring] 왜 엔티티에 Setter를 사용하면 안 될까? (0) | 2025.02.13 |