김영한님의 인프런 강의 '자바 ORM 표준 JPA 프로그래밍'을 참고했습니다.
페치 조인은 SQL에서 이야기하는 조인의 종류는 아니고 JPQL에서 성능 최적화를 위해 제공하는 기능이다. 이것은 연관된 엔티티나 컬렉션을 한 번에 같이 조회하는 기능인데 join fetch 명령어로 사용할 수 있다. JPA 표준 명세에 정의된 페치 조인 문법은 다음과 같다.
페치 조인에 대해 자세히 알아보자.
페치 조인을 사용해 회원 엔티티를 조회하면서 연관된 팀 엔티티도 함께 조회하는 JPQL을 보자. 회원과 팀 연관관계는 N:1이다.
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에서 조인의 결과다.
다음은 객체들의 엔티티 페치 조인 결과이다.
페치 조인을 쓴 후 조회를 해보자.
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:N이다.
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'
실행하면 다음과 같이 조인을 시도한다.
다음은 컬렉션 페치 조인 결과 테이블이다.
다음은 객체들의 엔티티 페치 조인 결과이다.
💡 여기서 핵심은!!
만약 중복을 허용하지 않고 싶다면 해결책으로 아래 DISTINCT가 있다.
select distinct t from Team t join fetch t.members where t.name = ‘팀A’
SQL의 DISTINCT : 중복된 결과를 제거하는 명령이다. 각 로우의 데이터가 모두 같아야 한다.
JPQL의 DISTINCT : 기존의 SQL DISTINCT기능 + 애플리케이션에서 엔티티 중복 제거
위의 예시에서 "팀A"들은 로우의 데이터가 다르므로 기존의 SQL DISTINCT 기능으로 못 지운다. 애플리케이션에서 같은 식별자를 가진 것을 확인하고 중복을 제거해 준다.
팀과 회원 연관 관계가 1:N인 컬렉션 페치 조인에서
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'
JPQL은 결과를 반환할 때 연관관계까지 고려하지 않는다. 단지 SELECT 절에 지정한 엔티티만 조회할 뿐이다.
@Entity
public class Team {
...
@OneToMany(fetch = FetchType.EAGER) //**
@JoinColumn(name = "Team_ID")
private Member memebr;
..
}
팀을 조회하면 회원까지 같이 조회되지만, 페치 조인이 필요하지 않는 경우에도 무조건 즉시 로딩이 실행돼 문제가 생길 수 있다.
페치 조인 대상에는 별칭을 줄 수 없다.
why : 페치 조인의 목적은 연관된 것들을 다 끌고 오는 것!!
하이버네이트는 가능, 가급적 사용 X.
둘 이상의 컬렉션은 페치 조인할 수 없다.
컬렉션을 페치 조인하면 페이징 API(setFirstResult, setMaxResults)를 사용할 수 없다.
연관된 엔티티들을 SQL 한 번으로 조회 - 성능 최적화.
엔티티에 직접 적용하는 글로벌 로딩 전략보다 우선함.
@OneToMany
(fetch = FetchType.LAZY) //글로벌 로딩 전략모든 것을 페치 조인으로 해결할수 는 없음.
페치 조인은 객체 그래프를 유지할 때 사용하면 효과적.
if 여러 테이블을 조인해서 엔티티가 가진 모양이 아닌 전혀 다른 결과를 내야 하면,
📌 모두 지연 로딩으로 하고 최적화가 필요할 때 페치 조인 적용
📌 패치 조인은 상당히 중요. 실무에서 굉장히 많이 씀