※ 실무에서 정말정말 중요함
※ 실무에서 제일많이 사용함
※ 페치 조인은 객체 그래프를 SQL 한번에 조회하는 개념
※ 대부분의 N+1 문제는 페치 조인으로 해결이 됨
- SQL 조인 종류가 아님
- JPQL에서 성능 최적화를 위해 제공하는 기능
- 연관된 엔티티나 컬렉션을 SQL 한번에 함께 조회하는 기능
- join fetch 명령어 사용
- 페치 조인 ::= [LEFT [ OUTER] | INNER] JOIN FETCH 조인 경로
- @ManyToOne(fetch = FetchType.LAZY) 으로 세팅을 해도 fetch 이 우선순위가 높아서 실행됨
회원을 조회하면서 연관된 팀도 함께 조회 (SQL 한번에)
SQL 을 보면 회원뿐만 아니라 (T.*)도 함께 SELECT
- 작성 쿼리
=====================================================================
select m from Member m join fetch m.team
=====================================================================
- 실행 쿼리
=====================================================================
select m.* , t.* from member m
innert join team t on m.team_id = t.id
=====================================================================
아래와 같이 실행하면 N+1 문제가 발생함
1은 처음이 실행한 쿼리이고 N은 가져온 갯수만큼 쿼리를 또 날리는 것을 의미함
- 작성 코드
아래의 코드에서는 String query = "select m From Member m"; 쿼리로 실행함
=====================================================================
Team tema1 = new Team();
tema1.setName("teamA");
em.persist(tema1);
Team tema2 = new Team();
tema2.setName("teamB");
em.persist(tema2);
Team tema3 = new Team();
tema3.setName("teamC");
em.persist(tema3);
Member member = new Member();
member.setUsername("회원1");
member.setAge(8);
member.setType(MemberType.ADMIN);
member.setTeam(tema1);
em.persist(member);
Member member2 = new Member();
member2.setUsername("회원2");
member2.setAge(8);
member2.setType(MemberType.ADMIN);
member2.setTeam(tema1);
em.persist(member2);
em.flush();
Member member3 = new Member();
member3.setUsername("회원3");
member3.setAge(8);
member3.setType(MemberType.ADMIN);
member3.setTeam(tema2);
em.persist(member3);
em.flush();
em.clear();
String query = "select m From Member m";
//String query = "select m From Member m join fetch m.team";
List<Member> result = em.createQuery(query , Member.class)
.getResultList();
for(Member resultMember : result){
System.out.println("member : " +resultMember.getUsername() +" , team : " + resultMember.getTeam().getName());
}
=====================================================================
- 실행 쿼리
=====================================================================
Hibernate:
/* select
m
From
Member m */ select
member0_.id as id1_0_,
member0_.age as age2_0_,
member0_.TEAM_ID as team_id5_0_,
member0_.type as type3_0_,
member0_.username as username4_0_
from
Member member0_
Hibernate:
select
team0_.id as id1_3_0_,
team0_.name as name2_3_0_
from
Team team0_
where
team0_.id=?
member : 회원1 , team : teamA
member : 회원2 , team : teamA
Hibernate:
select
team0_.id as id1_3_0_,
team0_.name as name2_3_0_
from
Team team0_
where
team0_.id=?
member : 회원3 , team : teamB
=====================================================================
- 작성코드는 위의 사용 전 예제와 똑같으나 쿼리문만 다름
=====================================================================
String query = "select m From Member m join fetch m.team";
=====================================================================
- 실행 쿼리
=====================================================================
Hibernate:
/* select
m
From
Member m
join
fetch m.team */ select
member0_.id as id1_0_0_,
team1_.id as id1_3_1_,
member0_.age as age2_0_0_,
member0_.TEAM_ID as team_id5_0_0_,
member0_.type as type3_0_0_,
member0_.username as username4_0_0_,
team1_.name as name2_3_1_
from
Member member0_
inner join
Team team1_
on member0_.TEAM_ID=team1_.id
member : 회원1 , team : teamA
member : 회원2 , team : teamA
member : 회원3 , team : teamB
=====================================================================
- 일대다 관계 , 컬렉션 페치 조인
- 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 t from Team t join fetch t.members";
List<Team> result = em.createQuery(query , Team.class)
.getResultList();
for(Team team : result){
//페치 조인으로 팀과 회원을 함께 조히ㅗ해서 지연 로딩 발생 안함
System.out.println("team = " + team.getName() + " | " + team.getMembers().size());
}
=====================================================================
- 실행 쿼리
=====================================================================
Hibernate:
/* select
t
from
Team t
join
fetch t.members */ select
team0_.id as id1_3_0_,
members1_.id as id1_0_1_,
team0_.name as name2_3_0_,
members1_.age as age2_0_1_,
members1_.TEAM_ID as team_id5_0_1_,
members1_.type as type3_0_1_,
members1_.username as username4_0_1_,
members1_.TEAM_ID as team_id5_0_0__,
members1_.id as id1_0_0__
from
Team team0_
inner join
Member members1_
on team0_.id=members1_.TEAM_ID
team = teamA | 2
team = teamA | 2
team = teamB | 1
=====================================================================
teamA는 하나지만 속한 Member는 두명이기때문에 teamA에 대한 데이터가 두개 생김
- 작성 코드
=====================================================================
String query = "select t from Team t join fetch t.members";
List<Team> result = em.createQuery(query , Team.class)
.getResultList();
for(Team team : result){
System.out.println("## team = " + team.getName() + " | " + team.getMembers().size());
for(Member resultMember : team.getMembers()){
System.out.println("## member : " + resultMember);
}
}
=====================================================================
- 실행 쿼리
=====================================================================
Hibernate:
/* select
t
from
Team t
join
fetch t.members */ select
team0_.id as id1_3_0_,
members1_.id as id1_0_1_,
team0_.name as name2_3_0_,
members1_.age as age2_0_1_,
members1_.TEAM_ID as team_id5_0_1_,
members1_.type as type3_0_1_,
members1_.username as username4_0_1_,
members1_.TEAM_ID as team_id5_0_0__,
members1_.id as id1_0_0__
from
Team team0_
inner join
Member members1_
on team0_.id=members1_.TEAM_ID
## team = teamA | 2
## member : Member{id=4, username='회원1', age=8, team=domain.Team@534e58b6, type=ADMIN}
## member : Member{id=5, username='회원2', age=8, team=domain.Team@534e58b6, type=ADMIN}
## team = teamA | 2
## member : Member{id=4, username='회원1', age=8, team=domain.Team@534e58b6, type=ADMIN}
## member : Member{id=5, username='회원2', age=8, team=domain.Team@534e58b6, type=ADMIN}
## team = teamB | 1
## member : Member{id=6, username='회원3', age=8, team=domain.Team@1df1ced0, type=ADMIN}
=====================================================================
위와같이 teamA 의 데이터가 중복으로 출력됨
- SQL의 DISTINCT는 중복된 결과를 제거하는 명령
- JPQL의 DISTINCT 2가지 기능 제공
- 1. SQL에 DISTINCT를 추가
- 2. 애플리케이션에서 엔티티 중복 제거
같은 식별자를 가진 Team 엔티티 제거
- SQL에 DISTINCT를 추가하지만 데이터가 다르므로 SQL 결과에서 중복 제거 실패
ex)
id : 1
name : teamA
memberId : 1
team_id : 1
name : 회원1
id : 1
name : teamA
memberId : 2
team_id : 1
name : 회원2
- 작성 코드
=====================================================================
String query = "select distinct t from Team t join fetch t.members";
List<Team> result = em.createQuery(query , Team.class)
.getResultList();
for(Team team : result){
System.out.println("## team = " + team.getName() + " | " + team.getMembers().size());
for(Member resultMember : team.getMembers()){
System.out.println("## member : " + resultMember);
}
}
=====================================================================
- 실행 쿼리
=====================================================================
Hibernate:
/* select
distinct t
from
Team t
join
fetch t.members */ select
distinct team0_.id as id1_3_0_,
members1_.id as id1_0_1_,
team0_.name as name2_3_0_,
members1_.age as age2_0_1_,
members1_.TEAM_ID as team_id5_0_1_,
members1_.type as type3_0_1_,
members1_.username as username4_0_1_,
members1_.TEAM_ID as team_id5_0_0__,
members1_.id as id1_0_0__
from
Team team0_
inner join
Member members1_
on team0_.id=members1_.TEAM_ID
## team = teamA | 2
## member : Member{id=4, username='회원1', age=8, team=domain.Team@1b495d4, type=ADMIN}
## member : Member{id=5, username='회원2', age=8, team=domain.Team@1b495d4, type=ADMIN}
## team = teamB | 1
## member : Member{id=6, username='회원3', age=8, team=domain.Team@32b0876c, type=ADMIN}
=====================================================================
- 일반 조인 실행시 연관된 엔티티를 함께 조회하지 않음
- JPQL은 결과를 반환할 때 연관관계 고려하지 않음
- 단지 SELECT 절에 지정한 엔티티만 조회할 뿐
- 여기서는 팀 엔티티만 조회하고 회원 엔티티는 조회하지 않음
- 페치 조인을 사용할 때만 연관된 엔티티도 함께 조회(즉시 로딩)
- 페치 조인은 객체 그래프를 SQL 한번에 조회하는 개념
- 일반 조인 JPQL
=====================================================================
select t from Team t join t.members m where t.name ='teamA'
=====================================================================
- 일반 조인 실행 쿼리
=====================================================================
Hibernate:
/* select
t
from
Team t
join
t.members m */ select
team0_.id as id1_3_,
team0_.name as name2_3_
from
Team team0_
inner join
Member members1_
on team0_.id=members1_.TEAM_ID
Hibernate:
select
members0_.TEAM_ID as team_id5_0_0_,
members0_.id as id1_0_0_,
members0_.id as id1_0_1_,
members0_.age as age2_0_1_,
members0_.TEAM_ID as team_id5_0_1_,
members0_.type as type3_0_1_,
members0_.username as username4_0_1_
from
Member members0_
where
members0_.TEAM_ID=?
## team = teamA | 2
## member : Member{id=4, username='회원1', age=8, team=domain.Team@4f94e148, type=ADMIN}
## member : Member{id=5, username='회원2', age=8, team=domain.Team@4f94e148, type=ADMIN}
## team = teamA | 2
## member : Member{id=4, username='회원1', age=8, team=domain.Team@4f94e148, type=ADMIN}
## member : Member{id=5, username='회원2', age=8, team=domain.Team@4f94e148, type=ADMIN}
Hibernate:
select
members0_.TEAM_ID as team_id5_0_0_,
members0_.id as id1_0_0_,
members0_.id as id1_0_1_,
members0_.age as age2_0_1_,
members0_.TEAM_ID as team_id5_0_1_,
members0_.type as type3_0_1_,
members0_.username as username4_0_1_
from
Member members0_
where
members0_.TEAM_ID=?
## team = teamB | 1
## member : Member{id=6, username='회원3', age=8, team=domain.Team@7ff8a9dc, type=ADMIN}
=====================================================================
- 페치 조인 JPQL
=====================================================================
String query = "select t from Team t join fetch t.members m";
=====================================================================
- 페치 조인 실행 쿼리
=====================================================================
Hibernate:
/* select
t
from
Team t
join
fetch t.members m */ select
team0_.id as id1_3_0_,
members1_.id as id1_0_1_,
team0_.name as name2_3_0_,
members1_.age as age2_0_1_,
members1_.TEAM_ID as team_id5_0_1_,
members1_.type as type3_0_1_,
members1_.username as username4_0_1_,
members1_.TEAM_ID as team_id5_0_0__,
members1_.id as id1_0_0__
from
Team team0_
inner join
Member members1_
on team0_.id=members1_.TEAM_ID
## team = teamA | 2
## member : Member{id=4, username='회원1', age=8, team=domain.Team@1b495d4, type=ADMIN}
## member : Member{id=5, username='회원2', age=8, team=domain.Team@1b495d4, type=ADMIN}
## team = teamA | 2
## member : Member{id=4, username='회원1', age=8, team=domain.Team@1b495d4, type=ADMIN}
## member : Member{id=5, username='회원2', age=8, team=domain.Team@1b495d4, type=ADMIN}
## team = teamB | 1
## member : Member{id=6, username='회원3', age=8, team=domain.Team@32b0876c, type=ADMIN}
=====================================================================