실무에서 정말 정말 중요한 Fetch Join...에 대해 학습하자!
(복습하고 또 복습하기)
Fetch Join
연관된 엔티티나 컬렉션을 한 번에 함께 조회하는 기능 ( = 즉시 로딩) ➡️ 성능 향상!
- 일반적인 SQL 조인 종류가 아니다!
- JPQL에서 제공하는 성능 최적화를 위한 Join 기능
- join fetch
Fetch Join 사용법
- JOIN FETCH 명령어로 사용
#1. OUTER JOIN (OUTER은 생략 가능!)
LEFT [OUTER] JOIN FETCH + 조인 경로
#2. INNER JOIN (INNER 생략 가능!)
[INNER] JOIN FETCH + 조인 경로
예시
상황)
회원(다) - 팀(일) 의 연관관계를 맺은 두 엔티티가 있다.
페치 조인을 통해, 회원 엔티티를 조회 시, 연관된 팀도 한 번에 조회할 수 있다.
# JPQL
select m from Member m join fetch m.team
위 JPQL의 실제 SQL문은 아래와 같다.
# SQL
select
M.*, T.*
from
member m
inner join
Team t
on m.Team_id = t.id
페치 조인 안쓰면?
만약, 아래와 같이 페치 조인을 안쓰면, 어떻게 될까?
// 페치 조인 X
...
String query ="select m from Member m ";
List<Member> resultList = em.createQuery(query, Member.class).getResultList();
for (Member m : resultList) {
System.out.println(m.getTeam());
}
...
기본적으로, JPA에서 Fetch 전략은 지연로딩을 권장하고, 대부분 지연로딩으로 설정한다.
지연로딩 전략에서 위 처럼 페치 조인 없이 실행할 경우, Member.getTeam()을 실행할 때, 다른 팀을 조회할 때마다 쿼리문이 날라간다(N + 1 문제 발생).
하지만, 지연로딩 상황에서 아래와 같이 페치 조인을 사용한다면,
//페치 조인 사용
String query ="select m from Member m join fetch m.team";
List<Member> resultList = em.createQuery(query, Member.class).getResultList();
아래와 같이 한 번에 연관된 엔티티도 가져온다!
대부분의 지연로딩 전략이 권장되므로, 페치 조인을 잘 활용하자!
컬렉션 패치 조인
위의 코드에서 Member가 아닌, Team을 기준으로 일 대 다 상황에서도 패치 조인을 할 수 있다.
코드 사용 예시
// JPQL
select t from Team t join fetch t.Members
// 실제 동작 SQL
SELECT
t.*, m.*
FROM
Team t
INNER JOIN
Member m
ON t.id = m.team_id
상황
회원1 - 팀A
회원2 - 팀A
회원3 - 팀B
예제 코드
String query ="select t from Team t join fetch t.members";
List<Team> resultList = em.createQuery(query, Team.class).getResultList();
System.out.println("---------------------");
for (Team s :
resultList) {
System.out.println(s + " >> " + s.getMembers());
}
System.out.println("---------------------");
결과
주의할 점
분명, Team은 "팀A"와 "팀B" 총 2개인데, 회원의 수만큼 총 3번의 조회가 이루어졌다.
즉, 팀A가 중복되어 저장되있다.
❇️ ("일:다" 조인만 중복이 일어나고, "다:일" 조인은 중복이 없다 !)
컬렉션 페치 조인에서 중복 제거
JPQL의 DINSTINCT 명령어를 사용하여, 위와 같은 중복을 제거할 수 있다.
(JPQL DISTINCT의 기능 = SQL의 DISTINCT + ENTITY 중복 제거)
코드 예시
String query ="select DISTINCT t from Team t join fetch t.members";
List<Team> resultList = em.createQuery(query, Team.class).getResultList();
System.out.println("---------------------");
for (Team s :
resultList) {
System.out.println(s + " >> " + s.getMembers());
}
System.out.println("---------------------");
출력
페치 조인과 일반 조인의 차이
일반 조인은 연관 엔티티를 가져오지 않는다.
➡️ 즉, 페치 없이 조인만 사용할 경우, 처음 말했던, N+1 문제가 일어난다.
페치 조인의 한계
1. 페치 조인 대상에는 별칭을 못 준다
(하이버네이트는 가능하지만, 권장 ❌)
# JPQL
select t from Team t join fetch t.members as m # "as m"처럼 별칭 ❌
2. 둘 이상의 컬렉션은 페치 조인 불가
( 데이터가 어마어마하게 뻥튀기 될 수 있다...)
3. 컬렉션을 페치 조인 ➡️ 페이징 API 불가 (setFirstResult & setMaxResult )
(왜? 데이터 중복때문에 뻥튀기 되니까... )
3.1 일대일, 다대일 같은 단일 값 연관 필드들은 가능
3.2 distinct를 사용해도, DB에서 넘어오는 데이터들은 중복이 완전히 제거되지 않기 때문에, 위험하다.
3.3 사실 가능은 하지만, 경로 로그를 남기며, 메모리에서 동작하고 매우 위험하다 !!
➡️ @BatchSize 애너테이션을 활용하자 !
➡️ 혹은 아래와 같이 글로벌 세팅으로 설정해주자.
<persistence.xml>
<property name="hibernate.default_batch_fetch_size" value="2000"/>
강의
https://www.inflearn.com/course/lecture?courseSlug=ORM-JPA-Basic
'JPA' 카테고리의 다른 글
JPQL의 내장함수와 사용자 정의 함수 (0) | 2024.03.10 |
---|---|
JPA 매핑 애너테이션들! (0) | 2024.03.07 |
[JPA+Spring] 개발 할 때, 신경써야할 체크리스트 (0) | 2023.06.20 |
[JPA] Named Query - 쿼리를 함수처럼 만들어보자 (0) | 2023.06.01 |
[JPA] 경로 표현 (0) | 2023.05.29 |