본문 바로가기
[JAVA]/JPA

JPQL - fetch join

by 팡펑퐁 2023. 5. 13.
728x90

ferch join

  • SQL 조인의 종류가 아니다.
  • JPQL에서 성능 최적화를 위해 제공하는 기능이다.
  • 연관된 엔티티나 컬렉션을 SQL 쿼리 한 번으로 함께 조회할 수 있는 기능이다.
  • join fetch라는 명령어를 사용한다.
-- JPQL
select m from Member m join fetch m.team

-- SQL
SELECT M.*, T.*
FROM MEMBER M
INNER JOIN TEAM T ON M.TEAM_ID = T.ID

 

 

컬렉션의 fetch join

  • 일대다 관계의 컬렉션 fetch join의 경우 데이터가 뻥튀기될 수 있다.
-- JQPL
select t
from Team t join fetch t.members
where t.name = '팀A'

-- SQL
SELECT T.*, M.*
FROM TEAM T
INNER JOIN MEMBER M ON T.ID = M.TEAM_ID
WHERE T.NAME = '팀A'
  • 원래는 팀이 2개이므로 결과 2개가 나와야하지만 팀A가 중복되어 3개가 나온다.
-- 결과 
team = 팀A|members=2
-> member = Member{id=3, username='회원1', age=0}
-> member = Member{id=4, username='회원2', age=0}
team = 팀A|members=2
-> member = Member{id=3, username='회원1', age=0}
-> member = Member{id=4, username='회원2', age=0}
team = 팀B|members=1
-> member = Member{id=5, username='회원1', age=0}

 

 

DISTINCT

  • SQL의 DISTINCT는 중복된 결과를 제거하는 명령어
  • JPQL의 DISTINCT는 2 가지 기능을 제공한다.
    • SQL에 DISTINCT를 추가한다.
    • 애플리케이션에서 엔티티 중복을 제거한다.
-- DISTINCT 추가
select distinct t
from Team t join fetch t.members
where t.name = '팀A'
  • 같은 식별자를 가진 Team 엔티티를 제거한다.
-- DISTINCT 추가시 결과 
team = 팀A|members=2
-> member = Member{id=3, username='회원1', age=0}
-> member = Member{id=4, username='회원2', age=0}
team = 팀B|members=1
-> member = Member{id=5, username='회원1', age=0}

 

 

fetch join과 일반 조인의 차이점

  • 일반 조인을 실행하면 연관된 엔티티를 함께 조회하지 않는다.
-- JPQL
select t
from Team t join t.members m
where t.name = '팀A'

-- SQL
SELECT T.*
FROM TEAM T
INNER JOIN MEMBER M ON T.ID=M.TEAM_ID
WHERE T.NAME = '팀A'
  • JPQL은 결과를 반환할 때 연관관계를 고려하지 않는다. 단지 SELECT 절에 지정한 엔티티만 조회할 뿐이다.
  • 위의 예시의 경우 팀 엔티티만 조회하고, 회원 엔티티는 조회하지 않는다.
  • fetch join을 사용할 때만 엔티티도 함께 조회한다.(즉시 로딩)

 

 

특징과 한계

  • fetch join의 대상에는 별칭을 줄 수 없다.
  • 하이버네이트는 가능하지만 사용을 권장하지는 않는다. 따라서 필요시에는 몇 개의 데이터만 조회하는 별도의 쿼리를 날리는 것이 좋다.
  • 둘 이상의 collection은 fetch join을 할 수 없다.
  • collection을 fetch join하면 페이징 API(setFirstResult, setMaxResult)를 사용할 수 없다.
    • 일대일, 다대일 같은 단일 값 연관 필드들은 fetch join을 해도 페이징이 가능하다.
    • 하이버네이트는 경고 로그를 남기고 메모리에서 페이징 할 수 있다.
    • 페이징을 사용해야 하는 collection의 경우 batchSize를 사용하여 해결하는 방법도 있다.
      • batchSize는 보통 1000이하로 설정한다.
  • 지연로딩이 설정되어 있어도 fetch join이 우선이므로 즉시 조회된다.(글로벌 로딩 전략보다 우선임)
  • 실무에서 글로벌 로딩 전략은 모두 지연로딩이다.
  • 최적화가 꼭 필요한 곳은 fetch join을 적용한다, N + 1 문제가 터지는 곳에는 fetch join을 적용한다로 생각하면 된다. 
    • 이 접근 방법으로 실무에서 DB 성능 문제의 70~80% 이상을 해결할 수 있다고 한다.(영한님 피셜)

 

 

정리

  • 모든 것을 fetch join으로 해결할 수는 없다.
  • 객체 그래프를 유지할 때 사용하면 효과적이다.
  • 여러 테이블을 조인해서 엔티티가 가진 모양이 아닌 전혀 다른 결과를 내야 한다면, fetch join보다는 일반조인을 사용하여 필요한 데이터 들만 조회해서 DTO로 반환하는 것이 효과적이다.

 

 

 

참고

김영한 - 자바 ORM JPA 프로그래밍

728x90

'[JAVA] > JPA' 카테고리의 다른 글

JPQL - 엔티티 직접 사용  (0) 2023.05.13
JPQL - 다형성 쿼리  (0) 2023.05.13
JPQL - 경로 표현식  (0) 2023.05.04
JPQL 함수  (0) 2023.05.03
JPQL 조건식 - CASE 식  (0) 2023.05.03