Spring/Spring Data JPA

@EntityGraph

민철킹 2021. 6. 14. 17:44

Member와 Team이 다대일 관계로 매칭되어 있고, 지연로딩으로 설정되어있다고 생각해보자.

지연로딩의 특성상 Member를 가져올 때 Team에 대한 정보는 가져오지 않고 proxy 객체를 만들어놓고 Team에 대한 접근이 들어올 때 진짜 Team에 대한 정보를 가져온다.

 

이렇게 코드가 작성되어 있을 때, 쿼리가 어떻게 나갈까?

먼저 findAll에 의해 한번에 모든 Member에 대한 조회가 이루어질 것이다.

그 후에 우리가 작성한 member의 이름을 출력하였다. 이 때 쿼리문을 살펴보면 team에 관한 데이터는 하나도 가져오지 않은 것을 볼 수 있는데 이것이 바로 proxy객체로 가져오기 때문이다. 즉, findAll이 실행되면 일단 Member만 DB에서 가져온다.

팀의 이름을 가져오기 위해 getName()이 수행되면 그때 Team에 접근을 한다. 따라서 쿼리문이 아래와 같이 수행된다.

==> proxy를 초기화한다라고 말함.

 

team 이름이 출력되고 다음 루프문을 통해 두번째 회원의 이름이 출력된다.

또 다시 getName()이 실행될 때 쿼리가 실행된다.

 

N + 1 문제가 발생! 

이를 해결하기 위해 JPA가 제공하는 Fetch조인을 사용하는데,

Fetch 조인을 사용하게 되면 member를 조회할 때 연관된 team을 한방 쿼리로 한번에 조회한다.

 

메서드만 findAll에서 findMemberFetchJoin으로 변경하고 동일한 테스트를 실행

한방 쿼리로 모두 가져오기 때문에 Member 엔티티 안에 있는 Team객체까지 생성해서 조회해주므로 N+1 문제를 해결할 수 있다. 프록시 객체가 아니라, 진짜 데이터가 채워져있는 엔티티이다.

 


Fetch 조인에 대해 복습을 해보았는데, 스프링 데이터 JPA는 Fetch 조인을 위해 @EntityGraph를 제공해준다.

@Query를 사용하여 JPQL을 직접 작성해주어도 되지만 훨씬 더 편리하게 사용할 수 있다.현재 위에서 문제가 발생한 findAll은 스프링 데이터 JPA에서 작성되어있는 것을 상속받아 사용하는 함수이다. 이는 현재 Member만 조회하므로 N + 1의 문제가 발생하였다.

@EntityGraph 애노테이션을 사용하는데 속성으로 attributePaths를 통해 fetch join으로 가져올 연관 엔티티를 적어주면 된다.

Override 뿐아니라 @Query를 사용할 때나, 메서드 이름으로 쿼리를 작성할 때도 사용이 가능하다.

결과적으로 내부적으로 모두 fetch join을 사용하는 것이다.

기본적으로 fetch join은 left outer join을 사용한다.

 

추가로 namedEntityGraph라는 기능을 제공하는데

@NamedEntityGraph(name = "Member.all", attributeNodes = @NamedAttributeNode("team"))
@Entity
public class Member {
```
```
}
//--------------------------------------------------------

@EntityGraph("Member.all")
@Query("select m from Member m")
List<Member> findMemberEntityGraph();

이 또한 동일하게 fetch join이 적용된다.

 

간단할 때는 @EntityGraph를 사용하고 쿼리가 복잡해진다면 JPQL에 fetch join을 사용하자.

반응형

'Spring > Spring Data JPA' 카테고리의 다른 글

확장 기능  (0) 2021.06.15
JPA Hint, Lock  (0) 2021.06.14
벌크성 수정 쿼리  (0) 2021.06.14
페이징과 정렬  (0) 2021.06.13
쿼리 메서드 기능  (0) 2021.06.11