SPRING

[Spring] N+1

도원좀비 2025. 4. 9. 19:52

🎯 N+1 문제란?

N+1 문제는 연관 관계가 설정된 엔티티를 조회할 때, 추가적인 쿼리가 반복적으로 발생하는 현상

 

List<Post> posts = postRepository.findAll(); // 1번 쿼리
for (Post post : posts) {
    System.out.println(post.getUser().getNickname()); // N번 추가 쿼리 발생
}

위 코드에서 getUser() 호출 시마다 쿼리가 발생해서 결과적으로 1 + N번의 쿼리가 발생


🤔 발생 이유

JPA에서는 연관 관계를 @ManyToOne, @OneToMany 등으로 설정할 수 있고, 이때 기본적으로 LAZY 로딩 전략을 사용

처음에는 Post만 조회하고, getUser() 등 연관 엔티티 접근 시 그때서야 쿼리를 날림


⚠️ N+1 발생 조건

조건 설명
연관 엔티티가 Lazy 로딩 상태 기본 설정
서비스 단에서 .getUser(), .getComments() 등 호출 추가 쿼리 발생
반복문 등에서 연관 접근 N개 쿼리 발


🕵️‍♂️ 확인 방법

 

  1. Hibernate SQL 로그 활성화
  2. 반복문 안에서 연관 객체 접근
  3. 콘솔에 쿼리 수 확인
spring:
  jpa:
    properties:
      hibernate:
        use_sql_comments: true
logging:
  level:
    org.hibernate.SQL: DEBUG
    org.hibernate.type.descriptor.sql: TRACE

 


✅ 해결 방법

1️⃣ DTO Projection 방식 (QueryDSL)

엔티티가 아닌 DTO로 직접 필요한 필드만 조회

queryFactory
  .select(Projections.constructor(PostDto.class,
      post.id,
      post.title,
      user.nickname
  ))
  .from(post)
  .join(post.user, user)
  .fetch();
  •  연관 객체를 .join()으로 명시
  •  쿼리 1번에 필요한 데이터 모두 조회
  •  N+1 완전 차단

2️⃣ Fetch Join 사용 (JPQL)

엔티티를 직접 조회해야 할 경우 사용 가능

@Query("SELECT p FROM Post p JOIN FETCH p.user")
List<Post> findAllWithUser();
  • 연관 객체도 함께 가져옴 (1번 쿼리)
  • 단, 컬렉션(@OneToMany)과 함께 쓰면 페이징 불가

3️⃣ @EntityGraph 사용

JPA 기능만으로 Lazy 로딩을 미리 fetch 하도록 지시

@EntityGraph(attributePaths = {"user"})
@Query("SELECT p FROM Post p")
List<Post> findAllWithUserGraph();
  • 선언형으로 간단
  • 기본 쿼리에 fetch join 붙는 효과
  • 복잡한 join 구조에는 한계

'SPRING' 카테고리의 다른 글

[Spring] 스프링 테스트 코드  (3) 2025.04.10
[Spring] 인덱싱  (0) 2025.04.09
[SPRING] Spring Security + JWT 인증 예외 처리 흐름  (1) 2025.04.01
[SPRING] JWT  (2) 2025.04.01
[SPRING] JPAQueryFactory  (0) 2025.03.30