페치조인(fetch join)
- JPQL에서 성능 최적화를 위해 제공하는 기능으로 SQL 조인 종류가 아님
- 연관된 엔티티나 컬렉션을 SQL 한 번에 함께 조회하는 기능
- join fetch 명령어 사용
N+1 문제
Team teamA = new Team();
teamA.setName("팀A");
em.persist(teamA);
Team teamB = new Team();
teamB.setName("팀B");
em.persist(teamB);
Member member1 = new Member();
member1.setUsername("회원1");
member1.setTeam(teamA);
em.persist(member1);
Member member2 = new Member();
member2.setUsername("회원2");
member2.setTeam(teamA);
em.persist(member2);
Member member3 = new Member();
member3.setUsername("회원3");
member3.setTeam(teamB);
em.persist(member3);
em.flush();
em.clear();
String query = "select m From Member m";
List<Member> result = em.createQuery(query,Member.class).getResultList();
for (Member member : result) {
System.out.println("member = " + member.getUsername() + " , " + member.getTeam().getName());
}
...
- 회원1(SQL)
- 회원2(1차캐시)
- 회원3(SQL)
- N+1 문제 발생 -> 페치 조인으로 해결
N+1 문제 해결
...
String query = "select m From Member m join fetch m.team";
List<Member> result = em.createQuery(query,Member.class).getResultList();
for (Member member : result) {
System.out.println("member = " + member.getUsername() + " , " + member.getTeam().getName());
}
...
- result는 프록시가 아닌 실제 데이터
- 지연로딩으로 설정해도 페치조인이 우선이므로 즉시 로딩
DISTINCT
- JPQL의 DISTINCT는 2가지 기능을 제공
- SQL에 DISTINCT 기능
- 애플리케이션에서 엔티티 중복 제거
- 사용 전
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 member : team.getMembers()){
System.out.println("member = " + member);
}
}
- 사용 후
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 member : team.getMembers()){
System.out.println("member = " + member);
}
}
- 결론적으로 위에서 말한 애플리케이션 중복 제거
- 같은 식별자를 가진 Team 엔티티가 제거 됨
일반 조인과 차이
- 일반 조인 실행 시 연관된 엔티티를 함께 조회하지 않음, 단지 SELECT 절에 지정한 엔티티만 조회
- 페치 조인 실행 시 연관된 엔티티도 함께 조회(즉시 로딩)
- 페치 조인은 객체 그래프를 SQL 한번에 조회하는 개념
한계
- 페치 조인 대상에는 별칭 불가
String query = "select t from Team t join fetch t.members m" // t.members의 별칭 m이 문제
- 둘 이상의 컬렉션은 패치 조인 불가
- **컬렉션을 페치 조인하면 페이징API(setFirstResult, setMaxResult) 사용 불가
- 엔티티에 직접 적용하는 글로벌 전략(@OneToMany(fetch=FetchType.LAZY) 등)보다 우선함
- 즉 글로벌 전략으로 지연 로딩 설정하여도 즉시 로딩
특징
- 성능 최적화가 가능 -> 연관된 SQL 한번에 조회하므로
- 객체 그래프를 유지할 때 사용하면 효과적
- 글로벌 로딩 전략은 모두 지연 로딩