흰 스타렉스에서 내가 내리지

[JPA] N+1 문제를 피할 수 있는 다양한 방법 본문

JPA

[JPA] N+1 문제를 피할 수 있는 다양한 방법

주씨. 2024. 4. 28. 03:19
728x90

1. 페치 조인 사용

  • 가장 일반적인 방법
select m from Member m join fetch m.orders

SELECT M.*, O.* FROM MEMBER M
INNER JOIN ORDERS O ON M.ID=O.MEMBER_ID

 

  • 일대다 조인을 했으므로 중복된 결과가 나타날 수 있다.
  • 따라서 JPQL 의 DISTINCT 를 사용해서 중복을 제거하는 것이 좋다.

 

2. 하이버네이트 @BatchSize

  • 하이버네이트가 제공하는 org.hibernate.annotations.BatchSize 어노테이션을 사용하면 연관된 엔티티를 조회할 때 지정한 size 만큼 SQL 의 IN 절을 사용해서 추가 조회한다.
  • 만약 조회한 회원이 10명인데 size=5 로 지정하면 2번의 SQL 만 추가로 실행한다. 
@Entity
public class Member{
    ...
    
    @org.hibernate.annotations.BatchSize(size =  5)
    @OneToMany(mappedBy = "member", fetch = FetchType.EAGER)
    private List<Order> orders = new ArrayList<>();
    
    ...
}

SELECT * FROM ORDERS
WHERE MEMBER_ID IN (
    ?, ?, ?, ?, ?
)
  • 즉시로딩으로 설정하면 조회시점에 10건의 데이터를 모두 조회해야 하므로 SQL 이 2번 실행된다.
  • 지연 로딩으로 설정하면 지연로딩된 엔티티를 최초 사용하는 시점에 다음 SQL 을 실행해서 5건의 데이터를 미리 로딩해둔다.
  • 그리고 6번째 데이터를 사용하면 추가로 SQL 을 실행한다.
application.yml 설정파일에
hibernate.default_batch_fetch_size 속성을 사용하면 애플리케이션 전체에 기본으로 @BatchSize 를 적용할 수 있다

 

 

3. @Fetch(FetchMode.SUBSELECT)

  • 하이버네이트가 제공하는 org.hibernate.annotations.Fetch 어노테이션에 FetchMode 를 SUBSELECT 로 사용하면 연관된 데이터를 조회할 때 서브 쿼리를 사용해서 N+1 문제를 해결한다.
@Entity
public class Member{
    ...
    
    @org.hibernate.annotations.Fetch(FetchMode.SUBSELECT)
    @OneToMany(mappedBy = "member", fetch = FetchType.EAGER)
    private List<Order> orders = new ArrayList<>();
    
    ...
}
  • 다음 JPQL 로 회원 식별자 값이 10을 초과하는 회원을 모두 조회해보자.
select m from Member m where m.id > 10
  • 즉시로딩으로 설정하면 조회 시점에, 지연 로딩으로 설정하면 지연 로딩된 엔티티를 사용하는 시점에 다음 SQL 이 실행된다.
SELECT O FROM ORDERS O
WHERE O.MEMBER_ID IN (
    SELECT M.ID
    FROM MEMBER M
    WHERE M>ID > 10
)

 

 

# 정리

  • 즉시 로딩은 사용하지 말고 지연 로딩을 사용하는 것을 권장한다.
  • 즉시 로딩의 가장 큰 문제는 성능 최적화가 어렵다는 점이다. 
    • 엔티티를 조회하다보면 즉시 로딩이 연속으로 발생해서 전혀 예상하지 못한 SQL 이 실행될 수 있다. 
  • 따라서 모두 지연로딩으로 설정하고 성능 최적화가 꼭 필요한 곳에는 JPQL 페치 조인을 사용하자. 
  • 기본값이 즉시로딩인 @OneToOne 과 @ManyToOne 은 fetch = FetchType.LAZY 로 설정해서 지연로딩 전략을 사용하도록 변경하자.