Spring

SpringData JPA, N+1 문제를 해결

sehunbang 2024. 3. 6. 20:28

N+1 문제란?

엔티티 하나을 조회하기 위해서 1:N 로 연관된 엔티티까지 조회 쿼리문이 N+1번 날라가는 이슈

 

 

이로 인해 아래와 같은 시스템에 심각한 성능 저하가 일어날 수 있다

  • Comment 조회 - 1번
  • Comment의 갯수(각 Comment가 가지고 있는 Board 조회) - N번

이렇게 하면 N+1번의 쿼리가 발생하는 것이다

 

 

해결방법 3가지:

  1. GlobalFetch
  2. Fetch Join
  3. EntityGraph

 

 

Global Fetch Strategy

글로벌 패치 전략이란, 엔티티를 생성할 때(컴파일 시점) 결정 되는 연관관계 전략이

 

해결방법은 @ManyToOne 속성에 fetch 속성으로 LAZY를 주면 된다

public class Comment {

    ...

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "thread_id")
    Board thread;
}

 

 

 

 

Fetch Join

조인할 때 연관된 엔티티나 컬렉션를 함께 조회하려고 할 때 사용한다 결과는 `EAGER`와 똑같지만 과정은 

다르다 `EAGER`의 경우에는 N+1 쿼리가 발생하지만 

Fetch Join 의 경우에는 한번이 쿼리문으로 해결이 가능하다.

 

Spring Data JPA 에서는 `@Query` 어노테이션을 이용하여 JPQL를 생성할 수 있다 사용하는 방법은 위와 동일하게 `join fetch` 뒤에 연관된 엔티티나 컬렉션을 적어주면 된다

public interface CommentRepository extends JpaRepository<Comment, Long> {

    @Query("select c from Comment c join fetch c.board")
    List<Comment> findAll();
}

 

 

 

EntityGraph - 현업 사용 (+ QueryDSL도 현업 사용)

@EntityGraph도 마찬가지로 EntityGraph 상에 있는 Entity들의 연관관계 속에서 필요한 엔티티와 컬렉션을 함께 조회하려고 할때 사용한다

 

public interface CommentRepository extends JpaRepository<Comment, Long> {

    @EntityGraph(attributePaths = {"thread"}, type = EntityGraph.EntityGraphType.LOAD)
    List<Comment> findAll();
}

 

  • Spring Data JPA에서 적용하려는 메소드 위에 `@EntityGraph` 어노테이션을 달고 옵션을 준다 
  • attributePaths는 같이 조회할 연관 엔티티명을 적으면 된다. 
  • (콤마)를 통하여 여러개를 줄 수도 있다

 

 

type은 EntityGraphType.LOAD, EntityGraphType.FETCH 2가지가 있다

  • LOAD : attributePaths에 정의한 엔티티들은 EAGER, 나머지는 글로벌 패치 전략에 따라 패치한다
    • 일단 attributePaths 는 EAGER, 나머지는 매핑 설정 따라서
  • FETCH : attributePaths에 정의한 엔티티들은 EAGER, 나머지는 LAZY로 패치한다
    • 나머지 는  FETCH  다 LAZY!!