Spring Boot · 2026-01-16

Spring Boot JPA 성능 튜닝과 N+1 해결

Spring Boot 환경에서 JPA 성능 튜닝 방법을 정리한다. 엔티티 매핑 최적화, N+1 해결 방법, Hibernate batch size 설정을 포함한 성능 최적화

작성일 : 2026-01-16 ㆍ 작성자 : 관리자
post
목차

개요

Spring Boot에서 JPA 성능 튜닝은 애플리케이션 응답성에 직접적인 영향을 준다. 초심자도 이해할 수 있도록 엔티티 설계부터 쿼리 최적화, Hibernate 배치 설정까지 핵심을 정리한다. 주요 대상은 엔티티 매핑과 N+1 문제 해결이며, 관련 설정과 예제를 통해 실제 적용 방향을 제시한다.

엔티티 매핑 기초와 성능 영향

엔티티 매핑은 데이터베이스 접근 패턴을 결정한다. 매핑 선택에 따라 불필요한 쿼리가 발생할 수 있으므로 설계 단계에서 고려가 필요하다.

지연 로딩(Lazy)과 즉시 로딩(Eager)

  • Lazy: 연관 엔티티를 실제 사용 시점에 조회하여 초기 로딩 비용을 낮춘다.
  • Eager: 연관 엔티티를 즉시 함께 조회하여 추가 쿼리 수를 줄이는 대신 초기 비용이 커진다.

기본 값으로 ManyToOne은 EAGER인 구현체도 있으므로 주의가 필요하다. 보통은 Lazy를 기본으로 두고, 필요한 시점에 명시적으로 조정하는 방식이 안정적이다.

컬렉션 매핑 주의

OneToMany, ManyToMany 컬렉션은 사용 패턴에 따라 메모리와 쿼리 패턴에 큰 영향을 준다. 컬렉션 크기가 큰 경우 페이징과 DTO 프로젝션을 고려한다.

N+1 문제 이해

N+1 문제는 루트 쿼리 1건과 연관된 엔티티를 각 행마다 추가 조회하는 문제다. 트래픽과 응답 시간이 급증하므로 조기에 발견하고 해결하는 것이 중요하다.

원인 분석

  • 엔티티가 Lazy로 설정되어 있고, 루프에서 연관 엔티티를 참조할 때 발생
  • JPA 구현체가 컬렉션을 개별 쿼리로 로드할 때 발생

JPA N+1 해결 방법

여러 해결책이 존재하며, 상황에 따라 적절한 방법을 선택하는 것이 핵심이다.

1) Fetch Join 사용

JPQL의 fetch join은 연관 엔티티를 한 번의 조인으로 조회한다. 성능 개선 효과가 크지만, 페이징과 함께 사용 시 주의가 필요하다.

String jpql = "select p from Post p join fetch p.comments where p.id = :id";

2) EntityGraph 사용

EntityGraph는 런타임에 fetch 계획을 제어한다. 특정 쿼리에서만 연관 엔티티를 즉시 로드하고자 할 때 유용하다.

@EntityGraph(attributePaths = {"comments"})
Optional<Post> findWithCommentsById(Long id);

3) DTO 프로젝션

필요한 컬럼만 조회하는 방식으로 불필요한 엔티티 초기화를 막는다. 복잡한 연관 관계를 단순화하여 쿼리 성능을 확보한다.

select new com.example.dto.PostDto(p.id, p.title, c.content)
from Post p join p.comments c where p.id = :id

4) Hibernate Batch Fetch 및 @BatchSize

여러 개의 지연 로딩 엔티티를 한꺼번에 묶어서 조회하면 N+1을 완화할 수 있다. 설정은 전역 또는 엔티티별로 가능하다.

# application.properties
spring.jpa.properties.hibernate.default_batch_fetch_size=100
spring.jpa.properties.hibernate.jdbc.batch_size=50

@Entity
@BatchSize(size = 50)
public class Comment { ... }

hibernate.default_batch_fetch_size는 엔티티 식별자를 묶어 한 번에 가져온다. jdbc.batch_size는 insert/update를 배치 처리할 때 사용된다. 두 설정의 목적이 다르므로 혼용 시 의도를 명확히 해야 한다.

실전 적용 순서

  • 로그와 모니터링으로 N+1 발생 지점 탐지
  • 문제가 발생한 엔티티에 대해 fetch 전략 점검
  • 우선 JPQL fetch join 또는 EntityGraph 적용으로 즉시 해결 시도
  • 컬렉션이 큰 경우 DTO 프로젝션과 페이징 도입
  • 여러 엔티티 로딩이 반복되는 지점에는 Hibernate batch 설정 적용

예제: 게시글-댓글 구조에서 N+1 해소

간단한 예제는 다음과 같다. Post와 Comment가 일대다 관계일 때 fetch join으로 댓글을 한 번에 로드한다.

@Entity
public class Post {
  @Id private Long id;
  private String title;
  @OneToMany(mappedBy = "post", fetch = FetchType.LAZY)
  private List<Comment> comments = new ArrayList<>();
}

// Repository
@Query("select p from Post p join fetch p.comments where p.id = :id")
Optional<Post> findPostWithComments(@Param("id") Long id);

모니터링과 검증

변경 후에는 쿼리 로그, APM, 데이터베이스 실행 계획을 확인한다. 특히 페이징이 필요한 경우 fetch join이 오히려 중복 레코드를 생성하므로 주의가 필요하다. 성능 개선 전후를 비교하여 부작용 여부를 검증해야 한다.

결론

spring boot jpa 성능 튜닝은 매핑 설계, 쿼리 전략, Hibernate 설정을 함께 고려해야 효과가 있다. jpa n+1 해결 방법으로는 fetch join, EntityGraph, DTO 프로젝션, 그리고 hibernate batch size 설정 등이 유효하다. 단계적으로 적용하고 모니터링으로 검증하는 방식이 안정적이다.

spring boot jpa 성능 튜닝 jpa n+1 해결 방법 hibernate batch size 설정 N+1 문제 fetch join EntityGraph DTO 프로젝션 JPA 성능 튜닝