✔️ N+1 문제
연관 관계가 설정된 엔티티를 조회할 때 1번의 쿼리로 N개의 데이터를 조회한 후, 각 데이터의 연관된 데이터를 추가 조회하면서 총 N+1번의 쿼리가 발생하는 문제이다. 이로 인해 성능이 저하되고 복잡도가 증가되며, 숨어있는 쿼리가 발생해 예측이 어렵다는 문제가 발생한다.
✔️ 발생 원인
JPA는 객체지향적으로 데이터를 다루기 때문에 특정 엔티티를 조회할 때 연관된 데이터는 이후 필요시에 가져오도록 설계되어 있다. 이를 지연 로딩이라고도 한다. 이러한 지연 로딩 전략으로 연관 관계가 설정된 엔티티를 조회할 때 각각 개별적인 추가 조회가 발생하게 된다.
✔️ 해결 방안
1. Fetch Join
JOIN FETCH 구문을 사용해 연관된 엔티티를 한 번에 조회하는 방식이다. 사용 예는 다음과 같다.
@Query("SELECT o FROM Order o JOIN FETCH o.items")
List<Order> findAllWithItems();
Fetch Join을 사용하면 한 번의 쿼리로 연관 객체까지 즉시 로딩하므로, DB 접근을 대폭 감소시킬 수 있다는 장점이 있다. 다만, 페이징과 함께 사용 시에는 전체 조회 후 페이징하므로 사용이 어렵다.
추가적으로, OneToManyFetch Join 시에는 중복이 발생할 수 있으므로 DISTINCT와 함께 다음과 같이 사용해야 한다.
@Query("SELECT DISTINCT m FROM Member m JOIN FETCH m.orders")
2. EntityGraph
@EntityGraph는 쿼리를 작성하지 않고 fetch 전략을 재정의할 수 있는 방법으로, 코드로 살펴보면 다음과 같다.
@EntityGraph(attributePaths = {"items"})
List<Order> findAll();
EntityGraph로 추가적인 선언 없이 연관 엔티티 조회가 가능해, N+1의 문제를 해결 가능하다. 이는 다양한 쿼리에서 재사용이 가능하며, 선언적 관리가 가능하다는 장점을 가지고 있다. 다만 복잡한 연관 관계에서는 가독성을 저하시킬 수 있다는 단점이 있으므로 사용에 주의해야 한다.
3. BatchSize
@BatchSize(size = 100)
private List<OrderItem> items;
연관 엔티티를 IN절로 묶어 한번에 조회하는 방법이다.
4. DTO 직접 조회
필요한 칼럼만 SELECT해서 DTO로 매핑하는 방식으로, 성능이 가장 우수하다.
✔️ 정리
JPA의 N+1 문제는 연관 엔티티 조회 시 불필요한 추가 쿼리가 발생하는 문제로, 주로 지연 로딩 때문에 발생한다. 이를 해결하기 위해 Fetch Join, EntityGraph, BatchSize, DTO 직접 조회 등 다양한 방법을 상황에 맞게 선택해야 한다. 성능에 민감한 서비스일수록 실행되는 쿼리를 직접 확인하며 N+1 문제를 해결하는 것이 중요하다고 할 수 있다.
'codeit sprint backend > weekly paper' 카테고리의 다른 글
| [10-1] 애플리케이션의 입력값 검증 전략 (0) | 2026.04.07 |
|---|---|
| [9-2] Transaction: 격리 (0) | 2026.04.01 |
| [6-2] Spring MVC (0) | 2026.02.27 |
| [6-1] AOP(Aspect Oriented Programming) (0) | 2026.02.27 |
| [5-2] Spring Boot의 Bean (0) | 2026.02.13 |