N+1 ๋ฌธ์ ๋ ๋ฌด์์ด๊ณ , ์ ๋ฐ์ํ ๊น??
: N+1 ๋ฌธ์ ๋ 1๋ฒ์ ์ฟผ๋ฆฌ๋ก N๊ฐ์ ์ํฐํฐ๋ฅผ ์กฐํํ ํ, ๊ฐ ์ํฐํฐ์ ์ฐ๊ด๋ ์ํฐํฐ๋ฅผ N๋ฒ ์ถ๊ฐ ์ฟผ๋ฆฌ๋ก ์กฐํํ๋ ์ํฉ!
โก๏ธ LAZY๋ก ์ํฐํฐ๋ฅผ ๋ง๋ค์์ ๋ ๋ฐ์ํจ!
(์ด๋ ๊ฒ ๋งํ๋ฉด ์ดํด๊ฐ ์ ๊ฐ์ง ์์ผ๋,,, ์์๋ก ์์๋ณด์)
SELECT * FROM todos;
-- Todo๋ฅผ ๋จผ์ ๊ฐ์ ธ์ค๊ณ (1๋ฒ)~ ๊ทธ๋ค์์ ๊ทธ์ ๋ง๋ ์ ์ ๋ ์ฐพ์์ค๊ธฐ(N๋ฒ) => N+1
SELECT * FROM users WHERE id = 1;
SELECT * FROM users WHERE id = 2;
...
SQL๋ก ๋ณด๋ฉด ์กฐ๊ธ ๋ ์ดํด๊ฐ ๊ฐ๋ค~!
๐ธ LAZY ๋ก๋ฉ์ด๋?
์คํ๋ง์ ์คํํ์๋ง์ ์ฐ๊ด๋ ์ํฐํฐ๋ฅผ ๋ฐ๋ก ๊ฐ์ ธ์ค์ง ์๊ณ ,
์ค์ ๋ก ๋ด๊ฐ "์ด๊ฑฐ ์ด์ ์ ๊ทผํด!" ๋ผ๊ณ ํ ๋ DB์์ ๊ฐ์ ธ์ค๋ ๋ฐฉ์(๊ทธ๋๊ทธ๋ ์กฐ๋ฌ)
์๋์ ์ฝ๋์ฒ๋ผ ์ค์ ๋์ด์์ ๋ ์คํ์์ ๊ฐ์ ธ์ค๋๊ฒ ์๋๋ผ,
todo.getUser()(์ ์ ๊ฐ์ ธ์!)๋ฅผ ์ฒ์ ํธ์ถํ ๋!!! ๋น๋ก์ User ์ ๋ณด๋ฅผ ๊ฐ์ ธ์ค๋ ์ฟผ๋ฆฌ๊ฐ ์คํ๋๋ค
@ManyToOne(fetch = FetchType.LAZY)
private User user;
์๋์ ์ฝ๋๋ Todo๋ฅผ ์กฐํํ๋ ์ฝ๋์ด๋ค.
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()
));
}
new UserResponse(todo.getUser().getId(), todo.getUser().getEmail()), ์ฌ๊ธฐ ์ฝ๋๋ฅผ ์์ธํ ๋ณด์
user๋ ํจ๊ป ๊ฐ์ ธ์ค๊ณ ์๋ ๋ชจ์ต์ ๋ณผ ์ ์๋ฐ!
๊ทธ๋ฌ๋ฉด Todo ๊ฐ์ฒด๋ฅผ ๊ฐ์ ธ์ฌ๋ user๋ ์กฐํ๋์ง ์์ ์ํ์๊ธฐ ๋๋ฌธ์(User๋ ์์ง ๋ก๋ฉx), map() ์์์ ์ ๊ทผํ ๋๋ง๋ค N๊ฐ์ ์ฟผ๋ฆฌ๊ฐ ๋ ์๊ฐ๊ฒ ๋๋ค,,,๐ฅน
์ด๋ฅผ ๋ฐฉ์งํ๊ธฐ ์ํด์ JPQL์ ํ์ฉํ๋ฉด ๋๋ค!!!
๋๊ฐ์ง ๋ฐฉ๋ฒ์ ์ฌ์ฉํ ์ ์๋ค โก๏ธ @Query & @EntityGraph
์ฝ๊ฐ JPQ + SQL๊ตฌ๋ฌธ์ผ๋ก ์๊ฐํ๋ฉด ํธํ ๊ฒ ๊ฐ๋ค!
๐ JPQL (Java Persistence Query Language)
JPA์์ SQL์ฒ๋ผ ์ฐ๋ "๊ฐ์ฒด ์งํฅ ์ฟผ๋ฆฌ ์ธ์ด"
์ฌ๊ธฐ์ SQL๊ณผ ์ฝ๊ฐ ๋ค๋ฅธ์ ์,
SQL โ ํ
์ด๋ธ ๊ธฐ์ค์ผ๋ก ์ง์
JPQL โ ์ํฐํฐ ๊ธฐ์ค์ผ๋ก ์ง์
์ ์ด์ @Query & @EntityGraph๋ฅผ ์ฌ์ฉํด์ ๊ฐ์ ํด๋ณด์!
@QueryMVC ๊ตฌ์กฐ์์ Repository์์ DB๋ฅผ ์กฐํํ ๋ ํ์ฉํ ์ ์๋๋ฐ,
์์ ์ฝ๋๋ก ์์๋ณด์
@Query("SELECT t FROM Todo t LEFT JOIN FETCH t.user u ORDER BY t.modifiedAt DESC")
Page<Todo> findAllByOrderByModifiedAtDesc(Pageable pageable);
@Query("SELECT t FROM Todo t " +
"LEFT JOIN FETCH t.user " + --> ์ฌ๊ธฐ
"WHERE t.id = :todoId")
Optional<Todo> findByIdWithUser(@Param("todoId") Long todoId);
์ฌ๊ธฐ์ LEFT JOIN FETCH t.user๋ ๋ฐ๋ก N+1 ๋ฌธ์ ํด๊ฒฐ์ ์ํ fetch join!
โก๏ธ ์ฆ, Todo๋ฅผ ์กฐํํ๋ฉด์ ๊ฐ Todo์ ์ฐ๊ฒฐ๋ User๋ ํ ๋ฒ์ ๊ฐ์ด ๋ก๋ฉ!!!
sql ๊ด์ ์์๋ ๋ ํ ์ด๋ธ์ ์กฐ์ธ์์ผ์ ๊ฐ์ ธ์ค๋ ๊ฒ๊ณผ ๋น์ทํ๋ค! => ํ๋ฒ์ ์กฐํ
SELECT t.*, u.*
FROM todos t
JOIN users u ON t.user_id = u.id;
SELECT t FROM Todo t๋ ์ค์ ๋ก๋ todos ํ ์ด๋ธ์ด์ง๋ง,
JPQL์์๋ Todo๋ผ๋ ์ํฐํฐ ํด๋์ค๋ฅผ ๊ธฐ์ค์ผ๋ก ์ฟผ๋ฆฌ ์ง๋ ๊ฒ!
๐ @EntityGraph๋?
LAZY์ค์ ๋ ์ฐ๊ด ์ํฐํฐ๋ฅผ ํ ๋ฒ์ ๊ฐ์ด ๋ก๋ฉ(Eager์ฒ๋ผ!)ํ๊ณ ์ถ์ ๋ ์ฌ์ฉํ๋ JPA์ ์ด๋ ธํ ์ด์
๐ JPQL์ fetch join๊ณผ ๋๊ฐ์ ๊ธฐ๋ฅ์ ์ ์ธ์ ์ผ๋ก ์ฒ๋ฆฌํด์ค!
์๊น ์ฝ๋๋ฅผ JPA์ @EntityGraph๋ก ๊ฐ์ ํด๋ณด์!
@EntityGraph(attributePaths = "user")
Page<Todo> findAllByOrderByModifiedAtDesc(Pageable pageable);
@EntityGraph(attributePaths = {"user"})
Optional<Todo> findById(@Param("todoId") Long todoId);
์๊น ๊ธธ์๋ ์ฝ๋๋ฅผ ์ด๋ ๊ฒ ์งง๊ฒ ์ค์ผ ์ ์๋ค!
๋ฌผ๋ก ๋๊ฐ๋ฅผ ํจ๊ป ์ฌ์ฉํ๋ ๊ฒ๋ ๊ฐ๋ฅํ๋ค
โก๏ธ JPQL + @EntityGraph ์กฐํฉ
๋ง์ฝ ์กฐ๊ฑด์ด ๋ณต์กํด์(๋ด๋ฆผ์ฐจ์, ์ฌ๋ฌ๊ฐ์ง ์กฐ๊ฑด๋ค,,) @Query๋ฅผ ์จ์ผ ํ๋ค๋ฉด, ์ด๋ ๊ฒ๋ ๊ฐ๋ฅ!
@EntityGraph(attributePaths = {"user"})
@Query("SELECT t FROM Todo t ORDER BY t.modifiedAt DESC")
List<Todo> findAllWithUserOrderByModifiedAt();
์ด ๊ฒฝ์ฐ์๋ @EntityGraph๊ฐ ์๋ํด์ t.user๋ฅผ ๊ฐ์ด fetch!
์ํฉ์ ๋ง์ถฐ์, ์ด๋ค ๋ฐฉ์์ด ๋ ์ฌ๋ฐ๋ฅผ์ง ๋ณด๊ณ ์ ํํ๋ฉด ๋๋ค.
| ๋น๊ต | JPQL โ 'fetch join' | JPA โ '@EntityGraph' |
|---|---|---|
| ์ ์ธ ๋ฐฉ์ | ์ฟผ๋ฆฌ ์ง์ ์์ฑ | ์ด๋ ธํ ์ด์ |
| ์ ์ง๋ณด์ | ๊ธธ์ด์ง ์ ์์ | ๊น๋ํ๊ณ ์ฌ์ฌ์ฉ ์ฌ์ |
| ๊ธฐ๋ฅ | ์์ ํ ์ ์ด ๊ฐ๋ฅ | ์ ์ธ์ , ๊ฐ๋จํจ |
| ์ฅ์ | ๋ณต์กํ ์กฐ๊ฑด ๊ฐ๋ฅ | ์ฝ๋ ๊ฐ๊ฒฐ |