본문 바로가기

Java

N + 1

N + 1

연관 관계가 설정된 엔티티를 조회할 경우 조회된 데이터 개수(N)개 만큼 연관관계의 조회 쿼리가 추가로 발생하는 현상

findAll 메서드

글로벌 패치 전략을 즉시로딩으로 설정 후 findAll메서드 실행 시

  • N + 1 문제 발생
  • findAll은 select u from User u라는 JPQL 구문을 생성해서 실행하기 때문
  • JPQL은 글로벌 패치 전략을 고려하지 않고 쿼리 실행
  • 모든 User를 조회하는 쿼리 실행 후 즉시 로딩 설정을 보고 연관관계 엔티티를 모두 조회

N + 1 해결법

fetch join

연관관계 있는 엔티티를 한번에 즉시로딩하는 구문

@Query("select distinct p from Post p join fetch p.comments")
List<Post> findWithPagination(Pageable pageable);

위와 같이 1 : N 관계의 컬렉션을 fetch join하면서 동시에 Pagination API를 사용하면 OutOfMemoryError가 발생할 수 있기 때문에, 이 둘을 동시에 사용해서는 안된다.
N : 1 관계일 때 사용

List<Comment> comments = entityManager
    .createQuery("select c from Comment c join fetch c.post", Comment.class)
    .setFirstResult(0)
    .setMaxResults(10)
    .getResultList();

~ToOne 관계일 때는 fetch join이 괜찮지만 ~ToMany 관계일 때는 Batch Size 지정

spring.jpa.properties.hibernate.default_batch_fetch_size=1000
@EntityGraph

fetch join과 비슷하며 쿼리 메소드에 해당 어노테이션을 추가해 사용
left outer join 만을 지원한다.

@Override
@EntityGraph(attributePaths = {"team"}) // 옵션 : 연관된 엔티티 지정
List<Member> findAll();

Pagination

한 페이지에 N개의 데이터만 보여주고 추가 요청이 있을 때 다음 순번의 N개 데이터를 보여주는 방식
UX 및 리소스 측면의 단점 보완

JPA Pagination API

DB 벤더에 따라 페이징 처리하는 쿼리가 달라진다.
MYSQL : LIMIT, OFFSET
ORACLE : 복잡한 쿼리로 페이지네이션 처리

@DisplayName("간단한 페이징을 적용해본다.")
@Test
void usePagination() {
    EntityManager entityManager = testEntityManager.getEntityManager();

    List<Post> posts = entityManager.createQuery("select p from Post p", Post.class)
        .setFirstResult(0)
        .setMaxResults(10)
        .getResultList();
}

Spring DATA JPA

Pageable 구현체를 쿼리 메서드의 파라미터로 전달함으로써 쿼리에 페이징을 동적으로 추가

@Query("select p from Post p")
List<Post> findWithPagination(Pageable pageable);
@DisplayName("Pageable을 사용하여 페이징 처리한다.")
@Test
void pagination() {
    postRepository.findWithPagination(Pageable.ofSize(10));
    postRepository.findWithPagination(PageRequest.of(0, 2));
}
  • 페이징 정렬 조건 까지 함께 지정 가능

일반적인 JPA 사용법

  1. 모든 연관관계는 기본적으로 지연 로딩(Lazy Loading)으로 설정합니다.
  2. N+1 문제가 발생하는 특정 조회 로직에서는 Fetch Join이나 @EntityGraph를 사용하여 PostComment를 처음부터 함께 조회하도록 쿼리를 최적화합니다. 이는 개발자가 필요한 데이터를 명시적으로 한 번의 쿼리로 가져오게 하여 N+1 문제를 근본적으로 해결하는 방법입니다.

'Java' 카테고리의 다른 글

오류 정리  (4) 2025.07.26
AOP  (1) 2025.07.24
Interceptor  (4) 2025.07.21
Spring 정리  (0) 2025.07.19
자바 계산기 구현  (4) 2025.07.12