GitHub - castle-bird/castle-bird-lab: ๋ง์ฃผํ ์ด์๋ค์ ํด๊ฒฐํ๊ณ ๊ธฐ๋กํ๋ ๊ณณ
๋ง์ฃผํ ์ด์๋ค์ ํด๊ฒฐํ๊ณ ๊ธฐ๋กํ๋ ๊ณณ. Contribute to castle-bird/castle-bird-lab development by creating an account on GitHub.
github.com
1. N+1์ด๋?
- JPA์ ๊ฐ์ ORM(๊ฐ์ฒด ๊ด๊ณ ๋งคํ) ๊ธฐ์ ์์ ๋ฐ์ํ๋ ์ฑ๋ฅ ๋ฌธ์
- ํ๋์ ์ฟผ๋ฆฌ(1)๋ฅผ ์คํํ์ ๋ ์ฐ๊ด๋ ๊ฐ์ฒด๋ฅผ ๋ถ๋ฌ์ค๊ธฐ ์ํด N๊ฐ์ ์ถ๊ฐ ์ฟผ๋ฆฌ๊ฐ ๋ฐ๋ณต์ ์ผ๋ก ์คํ
- ๋ฐ์ดํฐ๋ฒ ์ด์ค ๋ถํ์ ์ฑ๋ฅ ์ ํ๋ฅผ ์ผ๊ธฐ โญ
@ManyToOne(fetch = FetchType.LAZY)์ ๊ฒ์ผ๋ฅธ ๋ก๋ฉ์ ๋ฌธ์ ๊ฐ ๋ฐ์.
2. N+1์ ์์ธ ๋ฐ ์ฝ๋
์ฐ๊ด๋ ์ํฐํฐ๋ฅผ ๋ถ๋ฌ์ฌ ๋ ์ง์ฐ ๋ก๋ฉ(Lazy Loading) ์ ๋ต์ผ๋ก ์ธํด, ์ฒซ ์ฟผ๋ฆฌ ์ดํ ๊ฐ ๋ฐ์ดํฐ๋ง๋ค ์ถ๊ฐ ์ฟผ๋ฆฌ๊ฐ ๋ฐ์.
๐๋ฐ์ ์ฝ๋ (์๋จ GitHub์์ ์์ธํ ํ์ธ ๊ฐ๋ฅ โญ)
@Entity
@Table(name = "tbl_food")
public class Food {
// ... ํ๋
// ๋ถ๋ชจ ์ํฐํฐ ์ฐธ์กฐ ์ค. (Foreign Key)
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "category_id")
private Category category;
}
@Entity
@Table(name = "tbl_category")
public class Category {
// ... ํ๋
// ์์ ์ํฐํฐ์ ์ฐธ์กฐ ๋๋ ์ค.
@OneToMany(mappedBy = "category")
private List<Food> foods;
}
@RequiredArgsConstructor
@Service
public class MenuService {
private final FoodRepository foodRepository;
// ========== ์๋น์ค ๋ก์ง ==========
public List<FoodDTO> findAll() {
// 1. ์ด๋ ๊น์ง ์นดํ
๊ณ ๋ฆฌ ๋ด์ฉ์ด ์๋ ์์ Food์ ๋ด์ฉ๋ง ๋ด๊ฒจ์์
List<Food> foods = foodRepository.findAll();
// 2. ๊ทธ๋ฌ๋ DTO๋ณํ์ convertToDTO๋ด๋ถ ์ฝ๋๋ก์ธํด N + 1 ๋ฐ์ ์ค!
return foods.stream()
.map(this::convertToDTO)
.toList();
}
// ========== ์ํฐํฐ -> DTO ==========
public FoodDTO convertToDTO(Food food) {
// 3. CategoryId, CategoryName์ ์ ๊ทผ ์ํ์ฑ DTO ๋ณํ ์ N+1 ๋ฐ์ ์ํจ
FoodDTO foodDTO = FoodDTO.builder()
.id(food.getId())
.foodName(food.getFoodName())
.price(food.getPrice())
.build();
// 4. ์ด๋ ๋ฌธ์ ๋ฐ์.
// findAll()์ 1๋ฒ์์ ํ์ธํ๋ฏ์ด ์ต์ด์๋ Category๋ฅผ ์กฐํํ์ง ์์์.
// ๊ทธ๋ฌ๋ ํ์ฌ getCategory()์ ์ ๊ธํ๋ ์๊ฐ ์๋ Category๋ฅผ ์กฐํํ๊ธฐ ์ํด ์ถ๊ฐ ์ฟผ๋ฆฌ๊ฐ ๋ฐ์
if (food.getCategory() != null) {
foodDTO.setCategoryId(food.getCategory().getId());
foodDTO.setCategoryName(food.getCategory().getCategoryName());
}
return foodDTO;
}
}
์ ์ฝ๋ ์ฒ๋ผ N+1์ด ๋ฐ์ ํ์๋ ์๋ ์ด๋ฏธ์ง์ ๊ฐ์ด ์ต์ด ์กฐํ ์ดํ์๋ ์ฌ๋ฌ๋ฒ์ ์ฟผ๋ฆฌ๊ฐ ๋ฐ์ํ๋ ๊ฒ์ ์ ์ ์์.

3. N+1์ ํด๊ฒฐ๋ฒ
3-1. Fetch Join
- "๋ชจ๋ ๋ฐ์ดํฐ๋ฅผ ํ ๋ฐฉ์ ๊ฐ์ ธ์ค๊ธฐ"
JPQL์ ์ฌ์ฉํ์ฌ ์กฐํ ์์ ์ ์ฐ๊ด๋ ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ ๋ก ์กฐ์ธ(Join) ํ์ฌ ๋จ ํ ๋ฒ์ ์ฟผ๋ฆฌ๋ก ๊ฐ์ ธ์จ๋ค.- INNER JOIN ๊ธฐ์ค์ผ๋ก ๊ฐ์ ธ์ด โญ
// JPQL
@Query("SELECT f FROM Food f JOIN FETCH f.category")
List<Food> findAllJPQL();

3-2. EntityGraph
- "๊ฐ์ ธ์ฌ ๋ชฉ๋ก์ ๋ฏธ๋ฆฌ ์ฒดํฌ๋ฆฌ์คํธ๋ก ์ ๋ฌํ๊ธฐ"
- JPQL์ ์ง์ ์์ฑํ๋ ๊ฒ์ด ๋ฒ๊ฑฐ๋ก์ธ ๋ ์ฌ์ฉํ๋, JPA ํ์ค ์คํ์ธ ์ด๋ ธํ ์ด์ ๊ธฐ๋ฐ ๊ธฐ๋ฅ.
- LEFT JOIN ๊ธฐ์ค์ผ๋ก ๊ฐ์ ธ์ด โญ
// @EntityGraph
@Override
@EntityGraph(attributePaths = {"category"})
List<Food> findAll();

3-3. Batch Size
- "๋ฐ์ดํฐ๋ฅผ ๋ฌถ์ ๋ฐฐ์ก์ผ๋ก ๊ฐ์ ธ์ค๊ธฐ"
- ์์ ๋ ๋ฐฉ๋ฒ์ด ์ฟผ๋ฆฌ๋ฅผ '1๋ฒ'์ผ๋ก ์ค์ด๋ ๋ฐฉ๋ฒ์ด๋ผ๋ฉด,
Batch Size๋ N๋ฒ์ ์ฟผ๋ฆฌ๋ฅผ ๋งค์ฐ ๋ง์ด ์ค์ด๋ ๋ฐฉ์. - ์ฐ๊ด๋ ๋ฐ์ดํฐ๋ฅผ ์กฐํํ ๋
IN์ฟผ๋ฆฌ๋ฅผ ์ฌ์ฉํ์ฌ ์ค์ ํ ๊ฐ์๋งํผ ๋ชจ์์ ์กฐํ. - ๋ง์ฝ
Batch Size๋ฅผ 100์ผ๋ก ์ค์ ํ๋ค๋ฉด ์ฐ๊ด๋ ๋ฐ์ดํฐ ์กฐํ ์ฟผ๋ฆฌ์(N๋ฒ ์กฐํ), 100๊ฐ์ฉ ๋ฌถ์์ผ๋ก ์กฐํ.
// application.yml
spring:
jpa:
properties:
hibernate:
default_batch_fetch_size: 100

4. N+1์ ๋ฌด์กฐ๊ฑด ์์ ์ผ ํ๋ ๊ณผ์ ์ผ๊น?
N+1 ๋ฌธ์ ๋ฅผ ์ ๋ฐํ ์ ์๋ @ManyToOne(fetch = FetchType.LAZY) ์ค์ ์ด ๋ฌด์กฐ๊ฑด ๋์ ๊ฒ์ ์๋๋ค.
ํต์ฌ์ ๊ธฐ๋ฅ์ ๊ตฌํ๊ณผ ์ฑ๋ฅ์ ์ต์ ํ ์ฌ์ด์์ ์ ์ ํ ํฉ์์ ์ ์ฐพ๋ ๊ฒ์ด๋ค.
1) ์ ์ง์ฐ ๋ก๋ฉ(LAZY)์ ๊ธฐ๋ณธ์ผ๋ก ์ฐ๋๊ฐ?
- ๋ถํ์ํ ๋ฆฌ์์ค ๋ญ๋น ๋ฐฉ์ง: ์ฌ์ฉ์๊ฐ ํ์ธํ์ง๋ ์์ ๋ฐ์ดํฐ๊น์ง ํ๊บผ๋ฒ์ ์กฐํํ๋ ๊ฒ์ DB์ ๋ฉ๋ชจ๋ฆฌ์ ํฐ ๋ถ๋ด.
- ์์ธก ๊ฐ๋ฅ์ฑ: ์ฆ์ ๋ก๋ฉ(EAGER)์ ์ด๋์ ์ด๋ค ์ฟผ๋ฆฌ๊ฐ ๋๊ฐ์ง ํ์ ํ๊ธฐ ์ด๋ ต๊ฒ ๋ง๋ค์ด ์์์น ๋ชปํ ์ฑ๋ฅ ์ ํ ๋ฐ์.
2) ๊ธฐ์ ๋ณด๋ค ์ค์ํ ๊ฒ์ '์ฌ์ฉ์ ๋ถ์'
๊ฒฐ๊ตญ ์ด๋ค ๋ก๋ฉ ๋ฐฉ์์ ์ ํํ ์ง๋ ์ฌ์ฉ์์ ์๋น์ค ์ด์ฉ ํจํด์ด ์ค์
- ์์ธ ๋ณด๊ธฐ ์ง์ ๋ฅ ์ด ๋๋ค๋ฉด? ์ฒ์๋ถํฐ ๋ฐ์ดํฐ๋ฅผ ํจ๊ป ๊ฐ์ ธ์ค๋ ๊ฒ์ด ํจ์จ์ .
- ๋ชฉ๋ก๋ง ํ์ด๋ณด๋ ์ ์ ๊ฐ ๋ง๋ค๋ฉด? ํ์ํ ๋๋ง ๊ฐ์ ธ์ค๋ ์ง์ฐ ๋ก๋ฉ์ด ์ ๋ฆฌ.
3) ๊ฒฐ๋ก : ๋ฌด์กฐ๊ฑด์ ์ธ ์ ๊ฑฐ๋ณด๋ค๋ '์ ๋ณ์ ์ต์ ํ'
์ค์ ์ฌ์ฉ์ ๋ถ์์ ํตํด ์ฑ๋ฅ ๋ณ๋ชฉ์ด ๋ฐ์ํ๋ ํน์ ์ง์ ์์๋ง Fetch Join ๋ฑ์ ํ์ฉํด N+1์ ํด๊ฒฐํ๋ ์ ๋ต์ ์ทจํ๋ ๊ฒ์ด ๊ฐ์ฅ ๋ฐ๋์งํฉ๋๋ค.
๐
N+1 ํด๊ฒฐ์ ๊ธฐ์ ์ ์ธ '์ ๋ต'์ ๋งํ๋ ๋ฌธ์ ๊ฐ ์๋๋ผ,
๋น์ฆ๋์ค ์ํฉ์ ๋ง์ถฐ ํธ๋ ์ด๋์คํ(Trade-off)๋ฅผ ์กฐ์ ํ๋ ๊ณผ์ .
'Java' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
| [Java] ์ ๋ ฅ๊ฐ ๊ฒ์ฆ์ ๋ฒ์์ ์ฑ ์ (0) | 2026.02.01 |
|---|---|
| [Java] ๋ชจ๋ํฐ๋ง - Prometheus์ Grafana (0) | 2026.02.01 |
| [Java] @RestController์ HttpMassageConverter (0) | 2026.01.04 |
| [Java] @Controller ์ @RestController (0) | 2025.12.26 |
| [Java] AOP (0) | 2025.12.24 |