[본 포스팅은 자바 ORM 표준 JPA 프로그래밍 기본 편을 기반으로 작성하였습니다.]
페치 조인은 실무에서 가장 중요한 부분이다.
페치 조인(fetch join)
- SQL 조인 종류가 아님
- JPQL에서 성능 최적화를 위해 제공하는 기능
- 연관된 엔티티나 컬렉션을 SQL 한 번에 함께 조회하는 기능
- 즉, 나와 관련된 것들은 다 긁어옴
페치 조인과 일반 조인의 차이
- 일반 조인 실행 시 연관된 엔티티를 함께 조회하지 않음
- 페치 조인을 사용할 때만 연관된 엔티티도 함께 조회(즉시 로딩)
- 페치 조인은 객체 그래프를 SQL 한 번에 조회하는 개념
엔티티 페치 조인
@ManyToOne
상황) 멤버를 가져올 때 소속된 팀도 같이 가져오고 싶다.
[JPQL]
select m from Member m join fetch m.team
위와 같이 JPQL로 join fetch를 사용하고 select로 m만 가져와도 실행시켜 보면
[SQL]
SELECT M.*, T.*
FROM MEMBER M
INNER JOIN TEAM T
ON M.TEAM_ID = T.ID
위와 같이 멤버와 팀을 가져오는 SQL문이 나가는 것을 볼 수 있다.
※만약 JPQL로 "select m from Member m"으로 쿼리를 보내고 결괏값으로 받은 객체들에서 member.getTeam으로 팀을 가져오게 되면 쿼리가 N + 1이 되기 때문에 비효율적이다. 따라서 join fetch를 써야 됨.
자바 코드 적용
String query = "select m from Member m join fetch m.team";
List<Member> members = em.createQuery(query, Member.class)
.getResultList();
- 페치 조인으로 회원과 팀을 함께 조회해서 지연 로딩이 아니다.
컬렉션 페치 조인
@OneToMany
- 일대다 관계, 컬렉션 페치 조인
[JPQL]
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'
자바 코드 적용
String query = "select m from Member m join fetch m.team";
List<Member> members = em.createQuery(query, Member.class)
.getResultList();
- 페치 조인으로 회원과 팀을 함께 조회해서 지연 로딩이 아니다.
위 쿼리를 날려서 iter 돌려보면
위와 같이 팀 A가 두 번 나오게 된다.
그 이유는 아래의 사진처럼
팀 A에는 현재 두 명이 포함되어 있기 때문에 이것을 조회하면 그 수만큼 중복되어 나오게 된다.
그래서 중복을 없애려면 DISTINCT를 해주면 된다.(아래 참조)
페치 조인과 DISTINCT
- SQL의 DISTINCT는 중복된 결과를 제거하는 방법
- JPQL의 DISTINCT 2가지 기능 제공
- 1. SQL에 DISTINCT를 추가
- 2. 애플리케이션에서 엔티티 중복 제거
[JPQL]
select distinct t
from Team t
join fetch t.members
where t.name = '팀A'
JPQL에서는 distinct를 넣어주면 중복값을 제거해 준다.
그런데 SQL의 DISTINCT는 데이터가 완전히 일치해야 중복제거를 하지만 아래와 같은 데이터라면 제거를 해주지 않는다.
따라서 JPA에서는 다음과 같은 작업을 해준다.
- DISTINCT가 추가로 애플리케이션에서 중복 제거시도
- 같은 식별자를 가진 Team 엔티티 제거
컬렉션을 iter 돌린 결과
결과적으로 다대일은 중복값이 생기거나 그렇진 않지만 일대다는 중복값이 있을 경우 뻥튀기된다. 따라서 distinct를 해줘서 중복값을 없애줘야 한다.
페치 조인의 특징과 한계
- 페치 조인 대상에는 별칭을 줄 수 없다.
- 하이버네이트는 가능, 가급적 사용 X - 둘 이상의 컬렉션은 페치 조인 할 수 없다.
- 컬렉션을 페치 조인하면 페이징 API(setFirstResult, setMaxResults)를 사용할 수 없다.
- 일대일, 다대일 같은 단일 값 연관 필드들은 페치 조인해도 페이징 가능
- 하이버네이트는 경고 로그를 남기고 메모리에서 페이징(매우 위험) - 연관된 엔티티들을 SQL 한 번으로 조회 - 성능 최적화
- 엔티티에 직접 적용하는 글로벌 로딩 전략보다 우선함
- @OneToMany(fetch = FetchType.LAZY) //글로벌 로딩 전략 - 실무에서 글로벌 로딩 전략은 모두 지연 로딩
- 최적화가 필요한 곳은 페치 조인 적용
페치 조인 - 정리
- 모든 것을 페치 조인으로 해결할 수는 없음
- 페치 조인은 객체 그래프를 유지할 때 사용하면 효과적
- 여러 테이블을 조인해서 엔티티가 가진 모양이 아닌 전혀 다른 결과를 내야 하면, 페치 조인보다는 일반 조인을 사용하고 필요한 데이터들만 조회해서 DTO로 반환하는 것이 효과적
'JAVA > JPA' 카테고리의 다른 글
[JPA] 스프링 데이터 JPA로 벌크성 수정 쿼리 날려보기 (0) | 2023.01.02 |
---|---|
[JPA] 스프링 데이터 JPA 페이징과 정렬 (feat. DTO를 사용해야 하는 이유) (0) | 2023.01.02 |
[JPA] @MappedSuperclass로 공통 칼럼 상속받기 (0) | 2022.12.23 |
[JPA] 상속관계 전략과 매핑 (코드 예시) (0) | 2022.12.23 |
[JPA] 다대일(N:1), 일대다(1:N), 일대일(1:1) 연관관계 매핑 (코드 예시) (0) | 2022.12.23 |