[JPA] N+1 문제와 해결책

짱챌·2025년 4월 21일

JPA

목록 보기
5/5
post-thumbnail

JPA에서의 N+1 문제란?

N+1 문제는 연관관계가 설정된 1개의 객체를 조회할 때, 추가로 N개의 쿼리가 발생하는 현상을 말한다.

직접 쿼리문을 만들어 조회할 때는 하나의 쿼리만 실행된다.
하지만 JPA를 통해 객체를 조회하면, 연관된 다른 객체를 함께 조회하려는 과정에서 N+1 문제가 발생할 수 있다.

✅ 예시 상황

// 회원과 주문 (Member - Order: 1:N)

// Member.java
@OneToMany(mappedBy = "member")
private List<Order> orders = new ArrayList<>();

// Order.java
@ManyToOne
private Member member;

Member와 Order로 된 1:N 관계를 예시로 설명해보자!

즉시로딩 (EAGER)

글로벌 패치 전략을 즉시로딩으로 설정한 후,

findAll() 메소드를 사용하여 Member 객체를 조회하면, 다음과 같은 쿼리가 실행된다.

-select m from Member m -> 1번 실행

  • MemberOrder를 조회하는 쿼리 -> 회원 수(N)만큼 실행

즉, JPA는 Member를 조회하는 과정에서 해당 멤버가 가진 Order까지 즉시 로딩하려고 하며, 이로 인해 총 N + 1개의 쿼리가 발생한다.

지연로딩 (LAZY)

지연로딩으로 설정하면 프록시 객체가 주입되니, N+1 문제를 해결할 수 있을까?

  • findAll() 메소드를 사용하여 Member 객체를 조회하면 1개의 쿼리만 생성된다.

  • 하지만 이후 member.getOrders()와 같이 Member의 Order를 조회하는 순간, 각 프록시에 대한 쿼리(N)가 발생하게 된다.

결국 총 N + 1 쿼리 발생가 발생하며, 지연 로딩도 N+1 문제를 피할 수 없다.

N+1이 문제가 되는 이유

  • 조회 대상이 많아질수록 쿼리 수도 많아져 성능 저하
  • 의도하지 않은 다수의 쿼리 발생 -> DB 부하 증가

✅ 해결 방법

Fetch Join + 지연로딩

Fetch Join과 함께 지연로딩을 사용하면 N+1문제를 해결할 수 있다.

@Query("select m from Member m join fetch m.orders")
List<Member> findAllJPQLFetch();

fetch join을 사용하면 지연로딩으로 설정 되어있는 연관관계를 Join 쿼리로 한번에 조회할 수 있다.

@EntityGraph

@EntityGraph를 사용하면 JPQL을 직접 쓰지 않고, fetch join과 유사하게 동작하도록 할 수 있다.

@EntityGraph(attributePaths = {"orders"})
List<Member> findAll();

지연로딩을 부분적으로 즉시로딩으로 전환하는 기능이다.
여러 다대일 연관관계를 한번에 Join해올 수 있다.

profile
애옹: Magic Cat Academy

0개의 댓글