| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | 4 | 5 | 6 | 7 |
| 8 | 9 | 10 | 11 | 12 | 13 | 14 |
| 15 | 16 | 17 | 18 | 19 | 20 | 21 |
| 22 | 23 | 24 | 25 | 26 | 27 | 28 |
- Shard
- 서비스장애
- Gabage Collection
- 특성화고졸재직자
- 로드밸런서
- sticky session
- 전파옵션
- 트랜잭션 격리수준
- 일급컬렉션
- Kafka
- session storage
- pagnation
- transcation outbox
- JPA
- 분산트랜잭션
- request collapsing
- 엔티티
- recordlock
- 특성화고졸재직자편입
- SpringSecurity
- cache
- Scale Up
- 트랜잭션
- Scale out
- Fetch Join
- 분산 환경 세션 관리
- session clustering
- outbox
- 특성화고졸재직자후기
- N+1
- Today
- Total
hwasowl.log
JPA N+1 본문
JPA N + 1 문제는 연관 관계가 설정된 엔티티를 조회할 경우에, 조회된 데이터 개수(N)만큼 연관관계의 조회 쿼리가 추가로 발생하는 현상이다. 예를 들어, 블로그 게시글과 댓글이 있는 경우, 게시글을 조회한 후 각 게시글마다 댓글을 조회하기 위해 추가 쿼리가 발생한다면 N + 1 문제가 발생한 것이다. 댓글 10개가 달린 게시글 1개를 조회하는데 총 11개의 쿼리(게시글 조회 1개 + 각 게시글의 댓글 조회 10개)가 실행된다.
findAll 메서드의 글로벌 패치 전략 별 N + 1 문제 상황
글로벌 패치 전략을 즉시로딩으로 설정하고 findAll()을 실행하면 N + 1 문제가 발생한다. 이는 findAll()은 select u from User u라는 JPQL 구문을 생성해서 실행하기 때문이다. JPQL은 글로벌 패치 전략을 고려하지 않고 쿼리를 실행한다. 모든 User를 조회하는 쿼리 실행 후, 즉시로딩 설정을 보고 연관관계에 있는 모든 엔티티를 조회하는 쿼리를 실행한다.
글로벌 패치 전략을 지연 로딩으로 설정하고 findAll()을 실행하면 N + 1 문제가 발생하지 않는다. 이는 연관관계에 있는 엔티티를 실제 객체 대신에 프록시 객체로 생성하여 주입하기 때문이다. 하지만 프록시 객체를 사용할 경우에 실제 데이터가 필요하여 조회하는 쿼리가 발생하고 N + 1 문제가 발생할 수 있다.
N + 1 문제 해결 방법
N + 1 문제를 해결하기 위해서는 fetch join, @EntityGraph를 사용해 볼 수 있습니다. fetch join은 연관 관계에 있는 엔티티를 한번에 즉시 로딩하는 구문이다. @EntityGraph도 비슷한 효과를 만들어내며, 쿼리 메서드에 해당 어노테이션을 추가해 사용할 수 있다.

이해하기 쉽도록 상황을 재현해보기 위해 다대일 연관관계(User : 1, Article : N)를 만들어 설명해보겠다.
Fetch Join
// fetch join
select distinct u
from User u
left join fetch u.posts
@Test
@DisplayName("fetch join을 하면 N+1문제가 발생하지 않는다.")
void fetchJoinTest() {
System.out.println("== start ==");
List<User> users = userRepository.findAllJPQLFetch();
System.out.println("== find all ==");
for (User user : users) {
System.out.println(user.articles().size());
}
}

결과 이미지의 하이버네이트 로그를 보면 쿼리를 날릴 때 article을 한번에 모두 가져옴을 알 수 있습니다.
@EntityGraph
jpql에서 fetch join을 하게 된다면 하드코딩을 하게 된다는 단점이 있다. 이를 최소화하고 싶다면 @EntityGraph를 사용하면 된다.
@EntityGraph(attributePaths = {"articles"}, type = EntityGraphType.FETCH)
@Query("select distinct u from User u left join u.articles")
List<User> findAllEntityGraph();

Hibernate의 JPQL 구문에서의 fetch는 존재하지는 않지만 기존과 마찬가지로 fetch join을 통해 바로 조회할 수 있음을 알 수 있다.
더 이어서 보고싶다면
'기술면접' 카테고리의 다른 글
| 둘 이상의 컬렉션 조인 시 생길 수 있는 문제점 - MultipleBagFetchException (0) | 2024.12.02 |
|---|---|
| fetch join 사용 시 생길 수 있는 문제점 - Pagination (2) | 2024.12.01 |
| 트랜잭션 격리수준 (0) | 2024.11.26 |