Hibernate/SpringDataJPA

SpringDataJPA 쿼리메소드 N+1 LeftOuterJoin걸기 @EntityGraph

유혁스쿨 2023. 3. 17. 01:40
728x90
반응형

fetch정책과 N+1

 

JPA 기본 메소드인 findAll()의 경우 fetch정책이 LAZY인경우
N+1이 발생하는데, +1과 관련된 연관 엔티티를 조회하는 시점에 +1이 발생한다. 
(연관 엔티티를 따로 조회하지 않으면 연관관계의 주인이 되는 엔터티에 대한 조회만 발생)

but 쿼리메소드의 경우 fetch정책과 관계없이
N+1이 발생하는데, +1과 관련된 연관 엔터티를 함께 조회하지 않더라도 +1이 발생한다.
(연관관계의 주인이 되는 엔터티만 조회하더라도 연관 엔터티를 함께 조회한다)

 

N+1현상으로 인해 쿼리가 각각 조회되는것을 LEFT JOIN으로 한번에 조회하는 방법이 있다.

그것은 바로 FETCH JOIN이다.

FETCH JOIN

기본 메소드의 경우 연관관계 어노테이션 fetch옵션을 EAGER로 설정.

하지만 기본적으로 fetch옵션 정책은 Lazy로 설정해줘야 한다.

Lazy정책 에서의 fetch 조인법
기본 메소드(findAll 등)  Repository에 Override하고 @EntityGraph(attributePaths = {"recipe"}) 선언
JPQL  1. 쿼리의 LEFT JOIN 절 바로 뒤에 fetch 키워드를 붙힌다.
(" SELECT * FROM MASTER m [FETCH JOIN] m.연관테이블명")
2. 쿼리문상의 Join구문을 제거하고 @EntityGraph(attributePaths = ("recipe")) 선언
쿼리메소드 
(fetch가 EAGER 라도 LAZY로 반영됨)
Repository에 선언된 메소드에 @EntityGraph(attributePaths = "recipe")를 선언. 
   (연관관계에서 Eager 정책이면 Proxy오류 발생.)

 

예시 및 적용

Entity는 Diet 그리고 Recipe 입니다.

 

연관관계의 주인은 Diet Entity이고 조회는 Diet를 제너릭으로 등록한 Repository에서 조회를 합니다. (Diet엔티티를 조회한다는 뜻)

CrudRepository에서 제공하는 findById() 기본 jpa 함수를 적용했을 때는 LeftOuterJoin이 적용이 되는 쿼리가 실행됩니다.

JpaRepository를 상속받은 리포지토리에 쿼리메소드 방식으로 선언한 findBy~~~~ 형태의 커스텀 메소드를 적용하게 되었을때는 One to One 단방향 매핑 관계인 두 엔터티에 대해서 Join구문이 아닌 Select가 각각 두번씩 실행되는 현상이 발생합니다.

 

 


JpaRepository를 상속받은 Repository인터페이스에 선언된 쿼리메소드 위에 다음과 같이 @EntityGraph를 선언함으로써 해당 현상을 처리하는데 성공했습니다.

@EntityGraph(attributePaths = "연관테이블명")
리턴타입 쿼리메소드(...);

연관관계의 주인이되는 테이블은 Diet이고, 주인테이블로부터 연관을 갖는 테이블은 Recipe이므로 다음과같이 선언합니다.

@EntityGraph(attributePaths = "recipe")
List<Diet> findByUserIdAndDietDateAndMealDiv(String userId, Date stringToSqlDateFormat, Character mealDiv);

 

다음과 같은 설정으로정상적으로 LeftOuterJoin이 걸려 조회됩니다.

 

 


정리하자면 @ManyToOne에 의해 원치않는 다량의 JOIN발생 방지를 위해 FETCH정책을 EAGER(즉시로딩)가 아닌 LAZY(지연로딩)를 DEFAULT로 걸어둔다.

실무에는 테이블이 아주 많다, 테이블에서 @ManyToOne 연관관계가 5개 존재한다고 가정하였을 때, 이것들이 모두 EAGER로 설정되어 있다면 조인이 5개가 발생하기 때문이다.

 

이때, 만약 연관관계의 주인이 되는 테이블과, 연관 테이블을 함께 조회해야 할 때,

주인 테이블과 연관 테이블을 각각 따로 조회한다면 쿼리가 각각 두번 조회되는 N+1 현상이 발생하게 된다.

이 현상이 무조건적으로 발생하면 안되는것은 아니다.

다만, DIET와 같은 게시글의 데이터가 수천 수만개라고 가정한다면, DIET를 수천번 조회하고 그 수천 수만개의 DIET게시물 각각에 존재하는 수천개의 RECIPE를 조회하는 현상이 발생할 수 있다.

이것은 DBMS 서버를 이중으로 접근하는 셈이다.

 

따라서 기본적으로는 LAZY정책을 기본으로 N+1현상을 열어두고, JOIN이 필요한 부분에서 FETCH JOIN을 사용하여 N+1현상을 방지한다.

728x90
반응형