페치 조인
페치 조인은 SQL에서 사용하는 조인의 종류가 아니다.
JPQL에서 성능 최적화를 위해 제공하는 기능이다.
우리는 연관관계에서 지연 로딩을 사용한다.
하지만 페치 조인을 사용하게 되면 즉시 로딩을 하여 N+1 문제를 해결할 수 있다.
페치 조인을 할 때 나올 수 있는 값에 따라 2종류로 나눌 수 있다.
엔티티 페치 조인, 컬렉션 페치 조인
그렇다면 페치 조인을 사용할 때와 사용하지 않을 때를 비교하면서
총 4가지의 예시로 정리해보겠다.
연관관계를 그려보면 아래와 같다.
단일 값 일반 join
String query1 = "select m from Member m join m.team";
List<Member> resultList = em.createQuery(query1, Member.class)
.getResultList();
for (Member member : resultList) {
System.out.println("member = " + member.getUsername() + "," + "팀: " + member.getTeam().getName());
}
쿼리 순서
em.createQuery
SELECT MEMBER : MEMBER1, MEMBER2, MEMBER3
enhanced for문
SELECT TEAM : MEMBER1의 TEAM인 TEAM1을 조회하기 위한 쿼리
MEMBER2는 TEAM1이므로 영속성 컨텍스트에서 가져옴
SELECT TEAM : MEMBER3의 TEAM인 TEAM2을 조회하기 위한 쿼리
총 쿼리 횟수 : 3
단일 값 페치 join
String query2 = "select m from Member m join fetch m.team";
List<Member> resultList1 = em.createQuery(query2, Member.class)
.getResultList();
for (Member member : resultList1) {
System.out.println("member = " + member.getUsername() + "," + "팀: " + member.getTeam().getName());
}
쿼리 순서
em.createQuery
SELECT MEMBER : MEMBER1, MEMBER2, MEMBER3을 가져오면서 연관된 TEAM 객체를 같이 가져옴
총 쿼리 횟수 : 1
컬렉션 값 일반 join
String query3 = "select t from Team t join t.members";
List<Team> resultList2 = em.createQuery(query3, Team.class)
.getResultList();
for (Team team : resultList2) {
System.out.println("team: " + team.getName());
for (Member member : team.getMembers()) {
System.out.println("-> member = " + member.getUsername() + " ");
}
}
쿼리 순서
em.createQuery
SELECT TEAM : TEAM1, TEAM1, TEAM2
enhanced for문
SELECT MEMBER : TEAM1에 속해있는 MEMBER 객체들을 SELECT -> MEMBER1, MEMBER2 출력
영속성 컨텍스트에 저장되있는 MEMBER1, MEMBER2 출력
------------------------------------------------------------------------------------------------------------------------
SELECT MEMBER : TEAM2에 속해있는 MEMBER 객체들을 SELECT -> MEMBER3 출력
총 쿼리 횟수 : 3
컬렉션 값에서 중요한 부분이 있다.
처음 SELECT TEAM을 하게 되면 왜 TEAM1이 두 번 나오게 될까?
TEAM 테이블에서 멤버와 조인을 하는데 TEAM1에는 MEMBER가 두 명이 있게 된다.
그래서 TEAM을 기준으로 MEMBER를 조인하게 되면 총 3개의 인스턴스가 resultList2에 저장된다.
for문을 돌면 당연히 TEAM1이 두 번 조회되기 때문에 MEMBER1, MEMBER2는 각각 두 번식 호출된다.
컬렉션 값 페치 join
String query4 = "select t from Team t join fetch t.members";
List<Team> resultList3 = em.createQuery(query4, Team.class)
.getResultList();
for (Team team : resultList3) {
System.out.println("team: " + team.getName());
for (Member member : team.getMembers()) {
System.out.println("-> member = " + member.getUsername() + " ");
}
System.out.println();
}
쿼리 순서
em.createQuery
SELECT TEAM : TEAM1, TEAM2를 가져오면서 연관된 MEMBER들을 가져옴
총 쿼리 횟수 : 1
페치 조인을 사용해도 TEAM1이 두 번 저장되는 것은 같다!
---> DISTINCT 사용
페치 조인의 한계
페치 조인 대상에는 별칭을 주면 안된다.
String query1 = "select t from Team t join fetch t.members m where m.username = :name";
List<Team> resultList = em.createQuery(query1, Team.class)
.setParameter("name", "회원1")
.getResultList();
for (Team team : resultList) {
System.out.println("team = " + team);
for (Member member : team.getMembers()) {
System.out.println("member = " + member);
}
}
위에서 컬렉션 값 페치 조인을 보면 TEAM1에는 MEMBER1, MEMBER2가 저장되야한다.
왜?
fetch join의 결과는 연관된 모든 엔티티가 있을것이라 가정하고 사용해야하기 때문이다.
하지만 별칭을 사용한다면 TEAM1에는 MEMBER1만 존재하게 되어 객체 상태와 DB 상태 일관성이 깨지게 된다.
Reference
자바 ORM 표준 JPA 프로그래밍 (김영한 지음)
'Spring 강의 > JPA - 기본편' 카테고리의 다른 글
객체지향 쿼리 언어1 (1) ~ 프로젝션 (0) | 2021.12.14 |
---|---|
값 타입 (0) | 2021.12.12 |
프록시와 연관관계 관리(2) 즉시 로딩과 지연 로딩 ~ 영속성 전이 (0) | 2021.12.10 |
프록시와 연관관계 관리 (1) 프록시 (0) | 2021.12.10 |
고급 매핑 (0) | 2021.12.07 |