JPQL - 페치 조인

이상훈·2022년 10월 9일
0

Jpa

목록 보기
10/16

김영한님의 인프런 강의 '자바 ORM 표준 JPA 프로그래밍'을 참고했습니다.

페치 조인이란

페치 조인은 SQL에서 이야기하는 조인의 종류는 아니고 JPQL에서 성능 최적화를 위해 제공하는 기능이다. 이것은 연관된 엔티티나 컬렉션을 한 번에 같이 조회하는 기능인데 join fetch 명령어로 사용할 수 있다. JPA 표준 명세에 정의된 페치 조인 문법은 다음과 같다.

  • 페치 조인 ::= [ LEFT [OUTER] | INNER ] JOIN FETCH 조인 경로

페치 조인에 대해 자세히 알아보자.


엔티티 페치 조인

페치 조인을 사용해 회원 엔티티를 조회하면서 연관된 팀 엔티티도 함께 조회하는 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가 있다.


페치 조인과 DISTINCT

select distinct t from Team t join fetch t.members where t.name = ‘팀A’
  • SQL의 DISTINCT : 중복된 결과를 제거하는 명령이다. 각 로우의 데이터가 모두 같아야 한다.

  • JPQL의 DISTINCT : 기존의 SQL DISTINCT기능 + 애플리케이션에서 엔티티 중복 제거

위의 예시에서 "팀A"들은 로우의 데이터가 다르므로 기존의 SQL DISTINCT 기능으로 못 지운다. 애플리케이션에서 같은 식별자를 가진 것을 확인하고 중복을 제거해 준다.


페치 조인 vs 일반 조인 vs EAGER

팀과 회원 연관 관계가 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 절에 지정한 엔티티만 조회할 뿐이다.


  • 만약 다음과 같이 FetchType.EAGER을 사용하고 팀을 조회하면 어떨까?
@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 여러 테이블을 조인해서 엔티티가 가진 모양이 아닌 전혀 다른 결과를 내야 하면,

    • 여러 테이블에서 필요한 필드들만 조회해서 DTO로 반환하는 것이 더 효과적일 수 있다.

📌 모두 지연 로딩으로 하고 최적화가 필요할 때 페치 조인 적용
📌 패치 조인은 상당히 중요. 실무에서 굉장히 많이 씀

profile
Problem Solving과 기술적 의사결정을 중요시합니다.

0개의 댓글