흰 스타렉스에서 내가 내리지

페치 조인 :: join fetch 본문

JPA

페치 조인 :: join fetch

주씨. 2024. 4. 13. 02:39
728x90

# 페치 조인

  • JPQL 에서 성능 최적화를 위해 제공하는 기능이다.
  • 이것은 연관된 엔티티나 컬렉션을 한 번에 같이 조회하는 기능이다.

 

# 엔티티 페치 조인

  • 회원 엔티티를 조회하면서 연관된 팀 엔티티도 함께 조회하는 JPQL
SELECT m 
FROM Member m JOIN FETCH m.team
  • 회원 (m) 과 팀 (m.team) 을 함께 조회한다. 

* 실행된 SQL

SELECT
    M.*, T.*
FROM Member M
INNER JOIN Team T ON M.team_id = T.id

 

  • 엔티티 페치 조인 JPQL 에서 select m 으로 회원 엔티티만 선택했는데 실행된 SQL 을 보면 SELECT M.*, T.* 로 회원과 연관된 팀도 함께 조회된 것을 확인할 수 있다.

 

* 페치 조인을 사용하는 코드

String jpql = "SELECT m FROM Member m JOIN FETCH m.team";

List<Member> members = em.createQuery(jpql, Member.class)
    .getResultList();
   

for (Member member : members) {
    // 페치 조인으로 회원과 팀을 함꼐 조회해서 지연 로딩 발생 x
    member.getUsername();
    member.getTeam().getName();
}

 

  • 팀도 함께 조회했으므로, 연관된 팀 엔티티는 프록시가 아닌 실제 엔티티다. 
  • 따라서 연관된 팀을 사용해도 지연 로딩이 일어나지 않는다. 

 

 

# 컬렉션 페치 조인

  • 엔티티 페치 조인이 One 쪽 하나를 함께 조회하는 것이었다면,
    • 컬렉션 페치 조인은 Many, 즉 다 쪽(List) 를 함께 조회해 온다.
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'

 

  • jpql 에서 , select t 로 팀만 선택했는데, SQL 을 보면 T.*, M.* 로 팀과 연관된 회원도 함께 조회되었다.
  • 그 결과, 테이블에서 '팀A' 는 하나지만, MEMBER 테이블과 조인하면서 결과가 증가해서 같은 '팀A' 가 N 건 조회된다.

 

* 컬렉션 페치 조인 예제

이렇게 쓰면 안됨. 바로 아래 jpql 참고

String jpql = "select t from Team t join fetch t.members where t.name = '팀A'";

List<Team> teams = em.createQuery(jpql, Team.class).getResultList();

 

  • 출력 결과를 보면 '팀A' 가 2건 조회된 것을 확인할 수 있다. 

 

# 페치 조인과 DISTINCT

  • JPQL 의 DISTINCT 명령어는 SQL에 DISTINCT 를 추가하는 것은 물론이고, 애플리케이션에서 한 번 더 중복을 제거한다.
  • 바로 위 컬렉션 페치 조인은 팀A 가 중복으로 조회된다. 
select distinct t
from Team t join fetch t.members
where t.name = '팀A'

 

  • 먼저 DINSTINCT 가 SQL 에 추가된다
    • 하지만, 각 데이터는 모두 다르므로 SQL 의 DISTINCT 효과는 없다. 
  • 다음으로 애플리케이션에서 distinct 명령어를 보고 중복된 데이터를 걸러낸다. 
    • select distinct t 의 의미는 팀 엔티티의 중복을 제거하라는 뜻이다. 

 

# 페치 조인과 일반 조인의 차이

-- 내부 조인 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'

 

  • SQL의 SELECT 절을 보면 팀만 조회했고, 조인했던 회원은 전혀 조회되지 않는다. 
  • JPQL 은 단지 SELECT 절에 지정한 엔티티만 조회하기 때문에, 팀 엔티티만 조회하고 연관된 회원 컬렉션은 조회하지 않는다.
  • 반면에 페치 조인을 사용하면 연관된 엔티티도 함께 조회한다.

 

 

# 페치 조인의 특징과 한계

  • 페치 조인을 사용하면 SQL 한 번으로 연관된 엔티티들을 함께 조회할 수 있어서 SQL 호출 횟수를 줄여 성능을 최적화할 수 있다.
  • 글로벌 로딩 전략은 될 수 있으면 지연 로딩을 사용하고 최적화가 필요하면 페치 조인을 적용하는 것이 효과적이다.

 

  • 페치 조인 대상에는 별칭을 줄 수 없다.
    • 데이터 무결성이 깨질 수 있다.
  • 둘 이상의 컬렉션을 페치할 수 없다.
  • 컬렉션을 페치 조인하면 페이징 API (setFirstResult, setMaxResult) 를 사용할 수 없다.
    • 컬렉션이 아닌 단일 값 연관 필드들은 페치 조인을 사용해도 페이징 API 를 사용할 수 있다. 
    • 하이버네이트에서 컬렉션을 페치 조인하고 페이징 API 를 사용하면 경고 로그를 남기면서 메모리에서 페이징 처리를 한다.
      • 데이터가 적으면 상관없겠지만, 데이터가 많으면 성능 이슈와 메모리 초과 예외가 발생할 수 있어서 위험하다.

 

 

* 여러 테이블을 조인해서 엔티티가 가진 모양이 아닌 전혀 다른 결과를 내야 한다면, 억지로 페치 조인을 사용하기보다는 여러 테이블에서 필요한 필드들만 조회해서 DTO 로 변환하는 것이 더 효과적일 수 있다.