BE전문가 프로젝트
JPQL - 페치 조인(fetch join) 본문
- SQL 조인 종류X
- JPQL에서 성능 최적화를 위해 제공하는 기능
- 연관된 엔티티나 컬렉션을 SQL 한 번에 함께 조회하는 기능
- join fetch 명령어 사용
- 페치ㅣ 조인 ::=[LEFT[OUTER] | INNER] JOIN FETCH
엔티티 페치 조인
- 회원을 조회하면서 연관된 팀도 함께 조회(SQL 한 번에)
- 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
즉시로딩과 같지만 쿼리로 원하는 데로 객체 그래프를 한번에 조회할 것이라는 것을 명시적으로 동적인 타이밍에 정할 수 있다는 것이 차이점이다.
@Entity
@Getter
@Setter
public class Member {
@Id @GeneratedValue
private Long id;
private String username;
private int age;
@ManyToOne(fetch = LAZY)
@JoinColumn(name = "TEAM_ID")
private Team team;
}
Member에 fetch를 지연 로딩으로 설정해준다.
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("member1");
member1.setTeam(teamA);
em.persist(member1);
Member member2 = new Member();
member2.setUsername("member2");
member2.setTeam(teamA);
em.persist(member2);
Member member3 = new Member();
member3.setUsername("member3");
member3.setTeam(teamB);
em.persist(member3);
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());
//회원1, 팀A(SQL)
//회원2, 팀A(1차캐시)
//회원3, 팀B(SQL)
}
tx.commit();
실행을 하면 조회하는 쿼리는 총 3번나간다(Member조회, 회원1의 team조회, 회원3의 Team조회)
만약에 100명을 조회하게 되면 쿼리가 101번 나가는 경우가 생길 수도 있다. 이것을 n+1 문제라고 한다.
따라서 이것을 해결하는 경우는 join fetch를 통해 해결해야한다.
String query = "select m from Member m join fetch m.team";
List<Member> result = em.createQuery(query, Member.class)
.getResultList();
위에 있는 코드를 실행하면 이런 쿼리가 나오는데 Pintln으로 getTeam을 가져올 때 나오는 team은 프록시가 아닌 영속성컨텍스트에 있는 데이터이다.
지연로딩을 설정해도 fetch join이 우선으로 실행된다.
컬렉션 페치 조인
- 일대다 관계, 컬렉션 페치 조인
-- 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.memberList";
List<Team> result = em.createQuery(query, Team.class)
.getResultList();
for (Team team :result) {
System.out.println("member = " + team.getName() + ", " + team.getMemberList().size());
for (Member member : team.getMemberList()) {
System.out.println("-> member = " + member);
}
}
위에 있는 코드를 실행해보면 중복으로 팀A가 2번 나오는 것을 확인할 수 있다.
컬렉션일 때, 즉 일대다 join일 경우 일 때는 데이터가 뻥튀기가 된다.
JPA는 데이터를 전해주는 만큼 그래도 전해주기 때문에 팀A가 2개가 나온 것이다.
페치 조인과 DISTINCT
- SQL의 DISTINCT는 중복된 결과를 제거하는 명령
- JPQL의 DISTINCT 2가지 기능 제공
- 1. SQL에 DISTINCT를 추가
- 2. 애플리케이션에서 엔티티 중복 제거
- DISTINCT가 추가로 애플리케이션에서 중복 제거 시도
- 같은 식별자를 가진 Team 엔티티 제거
페치 조인과 일반 조인의 차이
- 일반 조인 실행시 연관된 엔티티를 함께 조회하지 않음
- JPQL은 결과를 반환할 때 연관관계 고려X
- 단진 SELECT 절에 지정한 엔티티만 조회할 뿐
- 여기서는 팀 엔티티만 조회하고, 회원 엔티티는 조회 X
- 페치 조인을 사용할 때만 연관된 엔티티도 함께 조회(즉시 로딩)
- 페치 조인은 연관된 엔티티를 함께 조회함
--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'
Team의 데이터만 가져오고 Member에 관련된 데이터는 가져오지 않는다.
Comments