๐ก
1. JPA์์์ ๊ฒ์๊ธ ๋ชฉ๋ก์ ์กฐํํ๋ ๋ฐฉ๋ฒ์ ํ์ตํ๋ค.
2. N+1 ๋ฌธ์ ์ ํด๊ฒฐ ๋ฐฉ๋ฒ ํ์ตํ๊ธฐ: ์ง์ฐ ๋ก๋ฉ์ผ๋ก ์ธํ N+1 ๋ฌธ์ ๋ฅผ ํ์ธํ๊ณ ํด๊ฒฐ ๋ฐฉ๋ฒ์ ๋ฐฐ์ด๋ค.
3. ๋ฐฐ์น ์ฌ์ด์ฆ(Batch Size) ์ค์ ์ดํดํ๊ธฐ: default_batch_fetch_size๋ฅผ ์ค์ ํ์ฌ ์ฑ๋ฅ์ ์ต์ ํํ๋ ๋ฐฉ๋ฒ์ ํ์ตํ๋ค.
4. ๊ฒ์๊ธ ๋ชฉ๋ก๋ณด๊ธฐ ์ปจํธ๋กค๋ฌ ๋ฐ ๋ทฐ ๊ตฌํํ๊ธฐ: ์ค์ ๋ก ๊ฒ์๊ธ ๋ชฉ๋ก์ ํ์ํ๋ ์ปจํธ๋กค๋ฌ์ ํ๋ฉด์ ์์ฑํ๋ค.
1. ๊ฒ์๊ธ ๋ชฉ๋ก๋ณด๊ธฐ ์ฟผ๋ฆฌ ์์ฑ (Eager Fetching)
๋ชฉํ: EAGER ํ์น ์ ๋ต์ ์ฌ์ฉํ์ฌ ๊ฒ์๊ธ ๋ชฉ๋ก์ ์กฐํํ๊ณ , ์ฐ๊ด๋ User ์ํฐํฐ๊ฐ ์ด๋ป๊ฒ ๋ก๋ฉ๋๋์ง ํ์ธํ๋ค.
package com.tenco.blog_v1.board;
import jakarta.persistence.EntityManager;
import jakarta.persistence.TypedQuery;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Repository;
import java.util.List;
@RequiredArgsConstructor
@Repository // IoC
public class BoardRepository {
private final EntityManager em;
/**
* ๊ฒ์๊ธ ์กฐํ ๋ฉ์๋
* @param id ์กฐํํ ๊ฒ์๊ธ ID
* @return ์กฐํ๋ Board ์ํฐํฐ, ์กด์ฌํ์ง ์์ผ๋ฉด null ๋ฐํ
*/
public Board findById(int id) {
return em.find(Board.class, id);
}
/**
* JPQL์ FETCH ์กฐ์ธ ์ฌ์ฉ - ์ฑ๋ฅ ์ต์ ํ
* ํ๋ฐฉ์ ์ฟผ๋ฆฌ๋ฅผ ์ฌ์ฉํด์ ์ฆ, ์ง์ ์กฐ์ธํด์ ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ ธ ์ต๋๋ค.
* @param id
* @return
*/
public Board findByIdJoinUser(int id) {
// JPQL -> Fetch join ์ ์ฌ์ฉํด ๋ณด์.
String jpql = " SELECT b FROM Board b JOIN FETCH b.user WHERE b.id = :id ";
return em.createQuery(jpql, Board.class)
.setParameter("id", id)
.getSingleResult();
}
/**
* ๋ชจ๋ ๊ฒ์๊ธ ์กฐํ
* @return ๊ฒ์๊ธ ๋ฆฌ์คํธ
*/
public List<Board> findAll() {
TypedQuery<Board> jpql = em.createQuery(" SELECT b FROM Board b ORDER BY b.id DESC ", Board.class);
return jpql.getResultList();
}
}
JPQL ์ฟผ๋ฆฌ: "SELECT b FROM Board b ORDER BY b.id DESC"
ํด์:
- SELECT b: Board ์ํฐํฐ๋ฅผ ์กฐํํ์ฌ b๋ผ๋ ๋ณ์นญ(alias)์ผ๋ก ์ ํํฉ๋๋ค.
- FROM Board b: ๋ฐ์ดํฐ ์์ค๋ก Board ์ํฐํฐ๋ฅผ ์ฌ์ฉํ๊ณ , ๋ณ์นญ b๋ฅผ ๋ถ์ฌํฉ๋๋ค.
- ORDER BY b.id DESC: b.id๋ฅผ ๊ธฐ์ค์ผ๋ก ๋ด๋ฆผ์ฐจ์ ์ ๋ ฌํฉ๋๋ค.
์์ฝ:
- Board ์ํฐํฐ์ ๋ชจ๋ ๋ฐ์ดํฐ๋ฅผ id ๋ด๋ฆผ์ฐจ์์ผ๋ก ์กฐํํฉ๋๋ค.
Board ์ํฐํฐ ์ฝ๋ ์์
@ManyToOne(fetch = FetchType.EAGER)
@JoinColumn(name = "user_id")
private User user; // ๊ฒ์๊ธ ์์ฑ์ ์ ๋ณด
BoardController ์์
@GetMapping("/")
public String index(Model model) {
//List<Board> boardList = boardNativeRepository.findAll();
// ์ฝ๋ ์์
List<Board> boardList = boardRepository.findAll();
model.addAttribute("boardList", boardList);
return "index";
}
index.mustache ์์
{{> layout/header}}
<div class="container p-5">
<!-- ๊ฒ์๊ธ ๋ชฉ๋ก์ ๋ฐ๋ณต ์ถ๋ ฅ (boardList๊ฐ null์ด ์๋๊ณ ๋น์ด ์์ง ์๋ค๋ฉด ์ถ๋ ฅ) -->
{{#boardList}}
<div class="card mb-3">
<div class="card-body">
<h4 class="card-title mb-3">{{title}} | ์์ฑ์ : {{user.username}}</h4>
<a href="/board/{{id}}" class="btn btn-primary">์์ธ๋ณด๊ธฐ</a>
</div>
</div>
{{/boardList}} <!-- ๋ฐ๋์ ์น์
์ ๋ซ๋ ํ๊ทธ๊ฐ ํ์ -->
<!-- ๊ฒ์๊ธ์ด ์์ ๊ฒฝ์ฐ ์ถ๋ ฅํ ๋ด์ฉ -->
{{^boardList}}
<p>๊ฒ์๊ธ์ด ์์ต๋๋ค.</p>
{{/boardList}}
<ul class="pagination d-flex justify-content-center">
<li class="page-item disabled"><a class="page-link" href="#">Previous</a></li>
<li class="page-item"><a class="page-link" href="#">Next</a></li>
</ul>
</div>
{{> layout/footer}}
1. N+1 ๋ฌธ์ ๋ ๋ฌด์์ธ๊ฐ์?
- N+1 ๋ฌธ์ ๋ ์ ํ๋ฆฌ์ผ์ด์ ์์ ํ ๋ฒ์ ์ฟผ๋ฆฌ๋ก N๊ฐ์ ์ํฐํฐ๋ฅผ ์กฐํํ ํ, ๊ฐ ์ํฐํฐ์ ์ฐ๊ด๋ ๋ค๋ฅธ ์ํฐํฐ๋ฅผ ์ง์ฐ ๋ก๋ฉ(Lazy Loading)์ผ๋ก ์กฐํํ ๋ ์ถ๊ฐ์ ์ธ N๊ฐ์ ์ฟผ๋ฆฌ๊ฐ ๋ฐ์ํ๋ ํ์์ ๋งํฉ๋๋ค.
- ๊ฒฐ๊ณผ์ ์ผ๋ก ์ด 1 + N๊ฐ์ ์ฟผ๋ฆฌ๊ฐ ์คํ๋๋ฉฐ, ์ด๋ ๋ฐ์ดํฐ๋ฒ ์ด์ค ๋ถํ์ ๋คํธ์ํฌ ํธ๋ํฝ ์ฆ๊ฐ๋ก ์ธํด ์ฑ๋ฅ ์ ํ์ ์์ธ์ด ๋ฉ๋๋ค.
2. ์ ๋ฌธ์ ๊ฐ ๋๋์?
- ์ฑ๋ฅ ์ ํ: ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ์ ํ๋ฆฌ์ผ์ด์ ๊ฐ์ ๋ถํ์ํ ํต์ ์ด ๋ง์์ ธ ์๋ต ์๊ฐ์ด ๊ธธ์ด์ง๋๋ค.
- ๋ฆฌ์์ค ๋ญ๋น: ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ์ฐ๊ฒฐ ์๊ฐ ์ฆ๊ฐํ๊ณ , CPU์ ๋ฉ๋ชจ๋ฆฌ ์ฌ์ฉ๋์ด ๋์ด๋ฉ๋๋ค.
- ํ์ฅ์ฑ ๋ฌธ์ : ๋ฐ์ดํฐ ์์ด ๋ง์์ง์๋ก(์: ๊ฒ์๊ธ์ด ์์ฒ ๊ฐ ์ด์) ์ฑ๋ฅ ์ ํ๊ฐ ๋์ฑ ์ฌ๊ฐํด์ง๋๋ค.
3. N+1 ๋ฌธ์ ํด๊ฒฐ (Batch Size ์ค์ )
์คํ๋ง JPA์์ default_batch_fetch_size ์ค์ ์ ๋ณต์กํ ์กฐํ์ฟผ๋ฆฌ ์์ฑ์, ์ง์ฐ๋ก๋ฉ์ผ๋ก ๋ฐ์ํด์ผ ํ๋ ์ฟผ๋ฆฌ๋ฅผ IN์ ๋ก ํ๋ฒ์ ๋ชจ์๋ณด๋ด๋ ๊ธฐ๋ฅ์ด๋ค.
๋ชฉํ: ์ง์ฐ ๋ก๋ฉ ์ ๋ฐ์ํ๋ N+1 ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํด default_batch_fetch_size ์ค์ ์ ์ฌ์ฉํ๋ค.
default_batch_fetch_size
- ์ง์ฐ ๋ก๋ฉ์ผ๋ก ๋ฐ์ํ๋ ์ฟผ๋ฆฌ๋ฅผ IN ์ ์ ์ฌ์ฉํ์ฌ ํ ๋ฒ์ ๋ชจ์ ๋ณด๋ผ ์ ์๋๋ก ํ๋ ์ค์ ์ด๋ค.
- ํ ๋ฒ์ ๊ฐ์ ธ์ฌ ์ํฐํฐ์ ์๋ฅผ ์ง์ ํ๋ค.
์คํ ์ฟผ๋ฆฌ ํ์ธ
Hibernate:
select
b1_0.id,
b1_0.content,
b1_0.created_at,
b1_0.title,
b1_0.user_id
from
board_tb b1_0
order by
b1_0.id desc
Hibernate:
select
u1_0.id,
u1_0.created_at,
u1_0.email,
u1_0.password,
u1_0.username
from
user_tb u1_0
where
u1_0.id in (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
Batch Size ์ค์ ๊ณผ Fetch Join(๊ฒ์๊ธ ์์ธ๋ณด๊ธฐ์์ ์ฌ์ฉ)์ ์ฐจ์ด์
Batch Size ์ค์
- ์ค๋ช : default_batch_fetch_size ์ค์ ์ ํตํด Hibernate๊ฐ ์ง์ฐ ๋ก๋ฉ ์ ์ฌ๋ฌ ์ํฐํฐ๋ฅผ ํ ๋ฒ์ ๋ก๋ฉํ๋๋ก ํฉ๋๋ค. ์ด๋ฅผ ํตํด N+1 ๋ฌธ์ ๋ฅผ ์ํํ ์ ์์ต๋๋ค.
- ๋์ ๋ฐฉ์: ์๋ฅผ ๋ค์ด, default_batch_fetch_size: 10์ผ๋ก ์ค์ ํ๋ฉด, Hibernate๋ ํ ๋ฒ์ ์ต๋ 10๊ฐ์ User ์ํฐํฐ๋ฅผ IN ์ ์ ์ฌ์ฉํ์ฌ ํ ๋ฒ์ ์ฟผ๋ฆฌ๋ก ๋ก๋ฉํฉ๋๋ค.
- ์ฟผ๋ฆฌ ์: ๋ฉ์ธ ์ฟผ๋ฆฌ 1๊ฐ + ๊ด๋ จ ์ํฐํฐ๋ฅผ ๋ฐฐ์น๋ก ๋ก๋ฉํ๋ ์ฟผ๋ฆฌ 1๊ฐ → ์ด 2๊ฐ์ ์ฟผ๋ฆฌ ์คํ.
Fetch Join ( ์ง์ฐ ๋ก๋ฉ๊ณผ ๊ด๊ณ ์์ด ์ฆ์ ๋ก๋ฉ ๋จ)
- ์ค๋ช : JPQL์์ JOIN FETCH๋ฅผ ์ฌ์ฉํ์ฌ ์ฐ๊ด๋ ์ํฐํฐ๋ฅผ ํ ๋ฒ์ ์ฟผ๋ฆฌ๋ก ํจ๊ป ์กฐํํฉ๋๋ค.
- ๋์ ๋ฐฉ์: Board์ User๋ฅผ ์กฐ์ธํ์ฌ ํ ๋ฒ์ ์ฟผ๋ฆฌ๋ก ๋ชจ๋ ๊ฐ์ ธ์ต๋๋ค.
- ์ฟผ๋ฆฌ ์: ๋ฉ์ธ ์ฟผ๋ฆฌ 1๊ฐ๋ก ๋ชจ๋ ๋ฐ์ดํฐ๋ฅผ ํ ๋ฒ์ ๋ก๋ฉ → ์ด 1๊ฐ์ ์ฟผ๋ฆฌ ์คํ.
Fetch Join์ ์ฌ์ฉํ ๋
- ์ฆ์ ๋ก๋ฉ์ด ํ์ํ ๊ฒฝ์ฐ: ์ฐ๊ด๋ ์ํฐํฐ๋ฅผ ์ฆ์ ๋ก๋ฉํ์ฌ ํ ๋ฒ์ ๋ชจ๋ ์ฌ์ฉํด์ผ ํ ๋.
- N+1 ๋ฌธ์ ์์ ํด๊ฒฐ์ด ํ์ํ ๋: ์ฐ๊ด ์ํฐํฐ๋ฅผ ๋ชจ๋ ํ ๋ฒ์ ๋ก๋ฉํ์ฌ ์ฟผ๋ฆฌ ์๋ฅผ ์ต์ํํ๊ณ ์ ํ ๋.
- ํ์ด์ง์ด ํ์ ์๋ ๊ฒฝ์ฐ: Fetch Join์ ์ฌ์ฉํ ๊ฒฝ์ฐ ํ์ด์ง๊ณผ์ ํธํ์ฑ ๋ฌธ์ ๊ฐ ๋ฐ์ํ ์ ์์ผ๋ฏ๋ก, ํ์ด์ง์ด ํ์ ์๋ค๋ฉด Fetch Join์ ์ฌ์ฉํ๋ ๊ฒ์ด ์ ๋ฆฌํฉ๋๋ค.
Batch Size ์ค์ ์ ์ฌ์ฉํ ๋
- ํ์ด์ง๊ณผ ํจ๊ป ์ฌ์ฉํ ๋: ํ์ด์ง์ด ํ์ํ ์ํฉ์์๋ Fetch Join ๋์ Batch Size ์ค์ ์ ์ฌ์ฉํ๋ ๊ฒ์ด ์ข์ต๋๋ค.
- ๋ถ๋ถ์ ์ผ๋ก ๋ก๋ฉ์ด ํ์ํ ๊ฒฝ์ฐ: ๋ชจ๋ ์ฐ๊ด ์ํฐํฐ๋ฅผ ํ ๋ฒ์ ๋ก๋ฉํ์ง ์๊ณ , ํ์ํ ๋งํผ๋ง ๋ฐฐ์น๋ก ๋ก๋ฉํ๊ณ ์ ํ ๋.
- ๋ณต์กํ ์กฐ์ธ ์์ด๋ ์ฑ๋ฅ ์ต์ ํ๊ฐ ํ์ํ ๊ฒฝ์ฐ: ๋ณต์กํ ์กฐ์ธ์ ์ฌ์ฉํ์ง ์๊ณ ๋ ์ฟผ๋ฆฌ ์๋ฅผ ์ค์ด๊ณ ์ ํ ๋ Batch Size ์ค์ ์ ํ์ฉํ ์ ์์ต๋๋ค.
์ฑ๋ฅ ๋น๊ต
Fetch Join์ ์ฌ์ฉํ ๊ฒฝ์ฐ
- ์ฟผ๋ฆฌ ์: 1๊ฐ (๊ฒ์๊ธ๊ณผ ์์ฑ์ ์ ๋ณด๋ฅผ ํจ๊ป ์กฐํ)
- ์ฑ๋ฅ: ์ผ๋ฐ์ ์ผ๋ก ๋ ๋น ๋ฆ, ๋คํธ์ํฌ ๋ฐ ๋ฐ์ดํฐ๋ฒ ์ด์ค ๋ถํ๊ฐ ์ค์ด๋ฌ
- ๋จ์ : ๋ฐ์ดํฐ ์ค๋ณต ๊ฐ๋ฅ์ฑ, ํ์ด์ง๊ณผ์ ํธํ์ฑ ๋ฌธ์
Batch Size ์ค์ ์ ์ฌ์ฉํ ๊ฒฝ์ฐ
- ์ฟผ๋ฆฌ ์: 2๊ฐ (๊ฒ์๊ธ ์กฐํ + ๋ฐฐ์น๋ก ์์ฑ์ ์กฐํ)
- ์ฑ๋ฅ: Fetch Join๋ณด๋ค๋ ์ฝ๊ฐ ๋๋ฆด ์ ์์ผ๋, ํ์ด์ง๊ณผ์ ํธํ์ฑ ๋ฑ ์ ์ฐ์ฑ์ด ๋์
- ์ฅ์ : ํ์ด์ง๊ณผ์ ํธํ์ฑ, ๋ฐ์ดํฐ ์ค๋ณต ๋ฌธ์ ์์