본문 바로가기

SpringFramework/JPA

JPA - N+1 문제

N+1 문제는 JPA를 사용하면서 성능상 가장 조심해야 하는 문제중 하나로 꼽히고 있다.

N+1 문제란 무엇인가?

@Entity
public class Order { // 주문정보
    @Id
    @GeneratedValue
    private Long id;
    
    @ManyToOne(fetch = FetchType.EAGER) // 즉시로딩 전략
    private Member members; // 주문한 회원
}

// ==========================================
public class OrderService {
    // ... EntityManager 생략

    Order order = em.find(Order.class, "1");
    /*
      SELECT o.*, m.*
      FROM Order o
      LEFT OUTER JOIN Member m ON o.id = m.id
      WHERE o.id = 1;
    */

    List<Order> orders = em.createQuery("select o from Order o", Order.class).getResultList();
    /*
      SELECT * FROM Order;
      SELECT * FROM Member WHERE id = ?;
      SELECT * FROM Member WHERE id = ?;
      SELECT * FROM Member WHERE id = ?;
      SELECT * FROM Member WHERE id = ?;
      ...
    */
}

=> 즉시로딩으로 Order(주문) 정보를 로딩할 때, Member 엔티티의 내용까지 미리 로딩해서 가져간다. 따라서 준영속 상태(프리젠테이션 계층)에서도 Member를 사용할 수 있다.

=> em.find()를 사용할 때에는 LEFT OUTER JOIN이 사용되었다.

=> JPQL을 사용한 경우에는 Order를 조회하고, Order의 members가 즉시로딩 이기때문에 엔티티 수만큼 SELECT 쿼리로 조회한다.

 

@ManyToOne(fetch = FetchType.EAGER)

즉, N+1 문제는 글로벌 페치 전략즉시로딩을 사용하고, JPQL로 엔티티를 호출하는 경우에 발생한다.

 

그렇다면, 지연로딩(fetch = FetchType.LAZY)으로 변경한다면 어떨까?

: 호출하는 시점이 실제 사용할 때 쿼리를 호출하지만, 엔티티(주문)와 관련된 엔티티(회원)를 호출할 때, 여러번의 SQL문을 호출하는건 변함이 없다.

 

= 왜 JPQL사용시, 글로벌 페치 전략은 N+1문제를 발생시킬까?

JPA는 JPQL을 분석해서 SQL을 생성할 때는 글로벌 페치 전략을 참고하지 않고, JPQL 자체로만 사용하여 SQL을 생성하기 때문이다. (즉, SQL을 만들때 설정 전략을 무시한다는소리)

반대로 JPA의 영속성 컨텍스트 제어 메소드 및 엔티티 호출 등은 글로벌 페치 전략을 참고하여 JPA가 SQL을 작성한다.

해결방법

JPQL fetch join으로 해결이 가능하다.

    List<Order> orders = em.createQuery("select o from Order o join fetch o.members", Order.class).getResultList();
    /*
      SELECT o.*, m.*
      FROM Member m
      INNER JOIN Order o ON m.id = o.member_id
    */

참고자료

- 서적 - 자바 ORM 표준 JPA 프로그래밍 제 15장 - 김영한 지음

'SpringFramework > JPA' 카테고리의 다른 글

QueryDSL 상위버전 설정 및 예제  (0) 2021.12.13
JPA - 트랜잭션과 락 (+격리수준)  (0) 2021.11.29
JPA - 스프링 데이터 JPA  (0) 2021.11.23
JPA - QueryDSL  (0) 2021.11.19
JPA - Criteria  (0) 2021.11.19