본문 바로가기

JAVA/- Spring

[spring boot] JPA에서 일반 Join과 Fetch Join의 차이

Join, Fetch Join 차이점 요약

  • 일반 Join
    • Fetch Join과 달리 연관 Entity에 Join을 걸어도 실제 쿼리에서 SELECT 하는 Entity는
      오직 JPQL에서 조회하는 주체가 되는 Entity만 조회하여 영속화
    • 조회의 주체가 되는 Entity만 SELECT 해서 영속화하기 때문에 데이터는 필요하지 않지만 연관 Entity가 검색조건에는 필요한 경우에 주로 사용됨
  • Fetch Join
    • 조회의 주체가 되는 Entity 이외에 Fetch Join이 걸린 연관 Entity도 함께 SELECT 하여 모두 영속화
    • Fetch Join이 걸린 Entity 모두 영속화하기 때문에 FetchType이 Lazy인 Entity를 참조하더라도
      이미 영속성 컨텍스트에 들어있기 때문에 따로 쿼리가 실행되지 않은 채로 N+1문제가 해결됨

 

여기서 N+1 문제가 뭘까요?

N+1 문제

연관 관계에서 발생하는 이슈로 연관 관계가 설정된 엔티티를 조회할 경우에 조회된 데이터 갯수(n) 만큼 연관관계의 조회 쿼리가 추가로 발생하여 데이터를 읽어오게 됩니다. 이를 N+1 문제라고 합니다.

 

다른 분의 블로그에서 가져온 글 입니다.

N+1 Problem은 쿼리 1번으로 N건의 데이터를 가져왔는데 원하는 데이터를 얻기 위해 이 N건의 데이터를 데이터 수 만큼 반복해서 2차적으로 쿼리를 수행하는 문제입니다.

제가 이해한 바로는 A와 B가 연관관계로 묶여있다면 A를 가져오려 하는데 B가 묶여있으니 A 한번조회(1) + B한번조회(1)이 되서 N+1이 된다고 합니다.

 

 

 

그럼 이 문제를 해결하기 위해서 Fetch Join을 사용할 수 있습니다.

이러한 객체가 있다고 생각해봅시다.

// TeamRepository.java
@Query("SELECT distinct t FROM Team t join t.members")
public List<Team> findAllWithMemberUsingJoin();

이 쿼리를 날렸을 때 

일반적으로 생각하는 Team과 Member가 join 된 형태의 쿼리가 실행되기는 합니다.

특이한 점은 가져오는 컬럼들을 보면 Team의 컬럼인 id와 name만을 가져오고 있습니다.

 

이 상태의 결과값을 toString()하게 되면 LazyInitializationException 이 발생하게 됩니다. 

왜냐하면 Team의 Lazy Entity인 member가 아직 초기화되지 않았다는 상태입니다.

 

실제로 일반 join은 실제 쿼리에 join을 걸어주기는 하지만 join대상에 대한 영속성까지는 관여하지 않습니다.

team은 영속성 컨텍스트에 있고 members는 영속성 컨텍스트에 관리되고 있지 않기 때문입니다.

즉, join의 대상까지 영속성 컨텍스트에 관리를 넣어주려면 Fetch Join을 하면 됩니다. 

 

// TeamRepository.java
@Query("SELECT distinct t FROM Team t join fetch t.members")
public List<Team> findAllWithMemberUsingFetchJoin();

이 쿼리를 날렸을 때 

이런식으로 select 하는 컬럼부터 차이가 납니다.

 

여기서 join과 fetch join의 차이를 볼 수 있는데요

  • 일반 Join : join 조건을 제외하고 실제 질의하는 대상 Entity에 대한 컬럼만 SELECT
  • Fetch Join : 실제 질의하는 대상 Entity와 Fetch join이 걸려있는 Entity를 포함한 컬럼 함께 SELECT

 

일반 Join는 언제 쓰나요?

어떻게 보면 무조건 Fetch Join이 좋아 보이기도 합니다. 하지만 일반 Join이 쓰임새도 분명 있습니다.

JPA는 기본적으로 "DB ↔ 객체" 의 일관성을 잘 고려해서 사용해야 하기 때문에
로직에 꼭 필요한 Entity만을 영속성 컨텍스트에 담아놓고 사용해야 합니다.

 

그러니 무작정 Fetch Join을 사용해서 전부 영속성 컨텍스트에 올려서 쓰기보다는
일반 Join을 적절히 이용하여 필요한 Entity만 영속성 컨텍스트에 올려서 사용하는 것이 괜한 오작동을 미리 방지할 수 있는 방법이기도 합니다.

 

연관 관계가 있는 Entity가 쿼리 검색 조건에는 필요하지만 실제 데이터는 필요하지 않은 상황에 좋다고 합니다.

이전 설명에서 일반 Join은 join대상에 대해서는 영속성 컨텍스트에 담지 않는다고 했습니다.

일반 Join의 이런 특성은 이번 예제에서 해결해야 할 상황에 적합해 보입니다.

 

출처

https://cobbybb.tistory.com/18#Fetch%--Join%EC%-D%--%--%EC%-D%B-%EC%-A%A-%ED%--%-C%--N%-B-%--%ED%--%B-%EA%B-%B--

728x90