발생 이유
N+1 문제는 주로 ORM(Object-Relational Mapping) 툴을 사용할 때 발생한다.
예를 들어, 한 개의 부모 엔티티와 여러 개의 자식 엔티티가 있는 관계에서 부모 엔티티를 조회할 때 자식 엔티티들도 함께 조회하는 상황을 생각해 보자. 여기서 발생하는 문제는 아래와 같다.
- 먼저 부모 엔티티를 조회하는 쿼리 실행. (1번의 쿼리)
- 그 다음 각 부모 엔티티에 대응하는 자식 엔티티들을 조회하기 위해 부모 엔티티 수만큼 추가 쿼리를 실행. (N번의 쿼리)
결국, 부모 엔티티 1개를 조회하기 위해 1번의 쿼리가 실행되고, N개의 자식 엔티티를 조회하기 위해 추가로 N번의 쿼리가 실행되어 총 N+1번의 쿼리가 실행되는 것.
이는 데이터베이스에 과도한 부하를 주고 응답 시간을 증가시키는 주요 원인이 된다.
그럼 해결 방법을 알아보자.
해결 방법
1. FetchType 변경하기
- EAGER 로딩: 연관된 엔티티를 처음부터 모두 로딩한다. 이 방법은 필요하지 않은 데이터까지 불러올 위험이 있으므로 신중히 사용해야 함.
- LAZY 로딩: 연관된 엔티티를 실제로 사용할 때까지 로딩을 지연시킨다. LAZY 로딩은 N+1 문제를 방지할 수 있으나, 적절한 해결책을 함께 사용해야 한다.
2. JPQL Fetch Join 사용하기
- Fetch Join: JPQL에서 제공하는 Fetch Join 문법을 사용하여 연관된 엔티티를 한 번의 쿼리로 함께 조회한다. 이 방법은 N+1 문제를 효과적으로 해결할 수 있다.
SELECT p FROM Parent p JOIN FETCH p.children
3. @EntityGraph 사용하기
- EntityGraph: 스프링 데이터 JPA에서 제공하는 @EntityGraph 어노테이션을 사용하여 연관된 엔티티를 함께 로딩하는 방법. 특정 엔티티 조회 시점에 어떤 연관 엔티티를 EAGER 로딩할지 설정할 수 있다.
@EntityGraph(attributePaths = {"children"})
List<Parent> findAll();
4. Batch Size 설정하기
- Batch Size: 하이버네이트 설정에서 @BatchSize 어노테이션 또는 hibernate.default_batch_fetch_size 구성을 사용하여 한 번에 로딩할 연관 엔티티의 수를 조절하는 방법. 이는 연관 엔티티를 조회할 때 N+1 쿼리 대신에 적은 수의 쿼리로 데이터를 로딩할 수 있게 해준다.
'개발 공부' 카테고리의 다른 글
영속성 컨텍스트? 1-1 (0) | 2024.03.11 |
---|---|
JPA의 더티 체킹 (0) | 2024.02.16 |
AOP, Interceptor, Filter (0) | 2024.01.31 |
스프링의 Lifecycle (0) | 2024.01.30 |
오버라이드와 오버로드 (2) | 2024.01.26 |