fetch join이란
- SQL 조인 종류 X
- JPQL에서 성능 최적화를 위해 제공하는 기능
- 연관된 엔티티나 컬렉션을 SQL 한 번을 통해 조회할 수 있다.
- 페치 조인 ::= [ LEFT [OUTER] | INNER ] JOIN FETCH 조인경로
엔티티 fetch join
- 회원을 조회하면서 연관된 팀도 함께 조회
- SQL을 보면 회원 뿐만 아니라 팀(T.*)도 함께 SELECT
-- [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
SQL
JPQL
fetch join 사용하는 이유
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
System.out.println("username = " + member.getUsername() + ", " +
"teamName = " + member.getTeam().name());
}
/* 출력
username = 회원1, teamname = 팀A
username = 회원2, teamname = 팀A
username = 회원3, teamname = 팀B
*/
이미 한번에 다 조회를 하면서 쿼리가 한번만 나가고 1차 캐시에 남은 데이터로 사용하기 때문에 데이터베이스에 부담을 줄일 수 있다.
컬렉션 Fetch Join
- 일대다 관계, 컬렉션 fetch join
- JPQL ( Team에 있는 List<Member> members 조회)
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'
조회 쿼리
fetch Join 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();
for(Team team : teams) {
System.out.println("teamname = " + team.getName() + ", team = " + team);
for (Member member : team.getMembers()) {
//페치 조인으로 팀과 회원을 함께 조회해서 지연 로딩 발생 안함
System.out.println(“-> username = " + member.getUsername()+ ", member = " + member);
}
}
/*
teamname = 팀A, team = Team@0x100
-> username = 회원1, member = Member@0x200
-> username = 회원2, member = Member@0x300
teamname = 팀A, team = Team@0x100
-> username = 회원1, member = Member@0x200
-> username = 회원2, member = Member@0x300
*/
저위에 그림처럼 SQL에서는 TeamA에서는 두개가 조회되는데 JPQL은 4개가 조회되는 걸까?
왜냐하면 select t에 있는 t는 하나의 객체이기 때문에 member는 members라는 리스트에 모두 담겨있기 때문에
루프를 돌면서 중복 데이터를 내뱉는 것이다.
중복 제거
• SQL의 DISTINCT는 중복된 결과를 제거하는 명령
• JPQL의 DISTINCT 2가지 기능 제공
- 1. SQL에 DISTINCT를 추가
- 2. 애플리케이션에서 엔티티 중복 제거
select distinct t
from Team t join fetch t.members
where t.name = ‘팀A’
• SQL에 DISTINCT를 추가하지만 데이터가 다르므로 SQL 결과 에서 중복제거 실패
DISTINCT가 추가로 애플리케이션에서 중복 제거시도
• 같은 식별자를 가진 Team 엔티티 제거
[DISTINCT 추가시 결과]
teamname = 팀A, team = Team@0x100
-> username = 회원1, member = Member@0x200
-> username = 회원2, member = Member@0x300
일반 조인과 페치 조인의 차이점
일반 조인
[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'
fetch join
- JPQL ( Team에 있는 List<Member> members 조회)
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'
• 페치 조인을 사용할 때만 연관된 엔티티도 함께 조회(즉시 로딩)
• 페치 조인은 객체 그래프를 SQL 한번에 조회하는 개념
fetch join의 한계
- fetch join은 대상에는 별칭을 줄 수 없음 (예시: join fetch t.members as m)
- 다만 하이버네이트에서는 가능하나 가급적 사용을 금지하도록 한다.
- JPA의 객체 그래프를 탐색한다는 것은 참조된 모든 엔티티를 가져오기 때문에 정합성을 보장하지 않는다.
- 둘 이상의 컬렉션은 fetchjoin 불가능 함. (예시: 팀의 맴버들과 협력사들을 동시에 가져와서 처리할 수 없다.)
- 컬렉션을 페치 조인 하면 페이징 api를 사용할 수 없다.
- 일대일이나 다대일 같은 단일 값 연관 필드는 페치 조인을 해도 상관없다.
- 한팀에 여러명이 있는데 팀을 9개씩 페이징한다고 생각하면 뭔가 모순되어 맞지 않다는 것을 느낄 것이다.
fetchjoin의 특징
- 연관된 엔티티들을 SQL한번으로 조회 가능 - 성능 최적화
- 엔티티에 직접 적용하는 글로벌 로딩 전략보다 우선함
- @OneToMany(fetch = FetchType.LAZY) 글로벌 로딩 전략
- 실무에서 글로벌 로딩 전략은 모두 지연 로딩
- 최적화가 필요한 곳은 fetch join사용
정리
- 모든 것을 fetch join으로 해결하기에는 한계가 존재함
- fetch join은 객체 그래프를 유지할 때 사용하면 효과적이다
- 다만 여러 테이블을 조회해서 다른 모양으로 변경을 해야 한다면 SQL보다 DTO로 반환하는 것이 효과적이다.
'JAVA > JPA' 카테고리의 다른 글
JPA 상속관계 (0) | 2022.09.24 |
---|---|
@OneToOne 관계에서 지연로딩(LAZY)이 안되는 문제 (0) | 2022.09.18 |
(JPA) 조인 (0) | 2021.07.09 |
(JPA) 페이징 (0) | 2021.07.09 |
(JPA) 프로젝션 (0) | 2021.07.08 |