반응형
본 내용은 온라인 강의 사이트 인프런의 김영한 님의 강의 내용이 포함되어 있습니다.
'자바 ORM 표준 JPA 프로그래밍 - 기본편'
페치 조인(Fetch Join)
페치 조인은 JPQL에서 성능 최적화를 위해 제공하는 기능이다.
- SQL 조인의 종류가 아니다.
연관된 엔티티나 컬렉션을 한 번에 같이 조회하는 기능으로 join fetch 명령어로 사용할 수 있다.
- 페치 조인 ::= [ LEFT [OUTER] | INNER ] JOIN FETCH 조인 경로
엔티티 페치 조인
페치 조인을 사용하여 회원 엔티티를 조회하면서 연관된 팀 엔티티도 함께 조회하는 경우
// 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
- join fetch 사용
- 회원(m)과 팀(m.team)을 함께 조회
- 페치 조인의 경우 별칭을 사용할 수 없다.
엔티티 페치 조인 예시
- 엔티티 페치 조인 JPQL 코드에서 select m으로 회원 엔티티만 선택하여 실행
- 실행된 SQL에서는 SELECT M.*, T.*로 Member와 연관된 Team도 함께 조회
- Member와 Team 객체가 객체 그래프를 유지하면서 조회된 것
페치 조인 사용 코드
String jpql = "select m from Member m join fetch m.team";
List<Member> members = em.createQuery(jpql, Member.class).getResultList();
for (Member member : members) {
System.out.println("username = " + member.getUsername() + ", " +
"teamname = " + member.getTeam().name());
}
// 출력
username = 회원1, teamname = 팀A
username = 회원2, teamname = 팀A
username = 회원3, teamname = 팀B
- 페치 조인을 사용하면 회원과 팀을 지연 로딩(LAZY)으로 설정해도 지연 로딩이 일어나지 않는다.
- 실제 엔티티를 조회하므로 회원 엔티티가 영속성 컨텍스트에서 분리되어 준영속 상태가 되어도 연관된 팀을 조회할 수 있다.
컬렉션 페치 조인
일대다 관계인 컬렉션을 페치 조인할 경우
// JPQL
select t from Team t fetch t.members where t.name = 'teamA'
// 실행된 SQL
SELECT T.*, M.* FROM TEAM T INNER JOIN MEMBER M ON T.ID=M.TEAM_ID WHERE T.NAME = '팀A'
- 팀(t)을 조회하면서 페치 조인을 사용하여 연관된 회원 컬렉션(t.members)도 함께 조회한다.
컬렉션 페치 조인 예시
- 컬렉션을 페치 조인한 JPQL에서 select t로 팀만 선택하여 실행
- 실행된 SQL에서는 SELECT T.*, M.*로 팀과 연관된 회원도 함께 조회
- TEAM 테이블에서 팀 A는 하나지만 MEMBER 테이블과 조인을 하면서 결과가 증가
- 팀 A가 2건 조회됨
컬렉션 페치 조인 사용 코드
String jpql = "select t from Team t join fetch t.members where t.name = '팀A'";
List<Team> teams = em.createQuery(jpql, Team.class).getResultList();
for (Team team : teams) {
System.out.println("teamname = " + team.getName() + ", " + "team = " + team);
for (Member member : team.getMembers()) {
System.out.println("->username = " + member.getUsername() + ", " + "member = " + member);
}
}
// 출력
teamname = 팀A, team = hellojpa.jpql.Team@1e60b459
->username = 회원1, member = Member{id=1, username='회원1', age=10}
->username = 회원2, member = Member{id=2, username='회원2', age=10}
teamname = 팀A, team = hellojpa.jpql.Team@1e60b459
->username = 회원1, member = Member{id=1, username='회원1', age=10}
->username = 회원2, member = Member{id=2, username='회원2', age=10}
- 컬렉션을 페치 조인하면 팀 A는 하나임에도 불구하고, MEMBER 테이블과 조인하면서 결과가 증가한다.
- 팀 A의 결과가 2건 조회된다.
- 팀과 회원을 지연 로딩(LAZY)으로 설정해도 지연 로딩이 일어나지 않는다.
- 이러한 문제를 해결하기 위해 DISTINCT로 중복된 결과를 제거할 수 있다.
페치 조인과 DISTINCT
SQL에서 DISTINCT는 중복된 결과를 제거하는 명령어이다.
JPQL의 DISTINCT 명령어는 2가지 기능을 제공한다.
- SQL에 DISTINCT를 추가
- 애플리케이션에서 엔티티 중복 제거
select distinct t from Team t fetch t.members where t.name = 'teamA'
teamname = 팀A, team = hellojpa.jpql.Team@4fe533ff
->username = 회원1, member = Member{id=1, username='회원1', age=10}
->username = 회원2, member = Member{id=2, username='회원2', age=10}
- DISTINCT가 추가로 애플리케이션에서 중복 제거를 시도한다.
- 같은 식별자를 가진 Team 엔티티를 제거(팀 A가 2번 조회되는 것을 방지)
페치 조인과 일반 조인의 차이
페치 조인을 사용하지 않는 경우
// JPQL
select t from Team t join t.members m where t.name = 'teamA'
// 실행된 SQL
SELECT T.* FROM TEAM T INNER JOIN MEMBER M ON T.ID=M.TEAM_ID WHERE T.NAME = '팀A'
->username = 회원1, member = Member{id=1, username='회원1', age=10}
->username = 회원2, member = Member{id=2, username='회원2', age=10}
->username = 회원1, member = Member{id=1, username='회원1', age=10}
->username = 회원2, member = Member{id=2, username='회원2', age=10}
- JPQL에서 팀과 회원 컬렉션을 함께 조회한다고 해도 연관된 회원 컬렉션도 함께 조회하지 않는다.
- JPQL은 결과를 반환할 때 연관관계까지 고려하지 않으며, SELECT 절에 지정한 엔티티만 조회한다.
- 지연 로딩(LAZY) 설정 시 프록시나 초기화하지 않은 컬렉션 래퍼를 반환한다.
- 즉시 로딩(EAGER)으로 설정 시 회원 컬렉션을 즉시 로딩하기 위해 쿼리를 한 번 더 실행한다.
페치 조인을 사용하는 경우
// JPQL
select t from Team t join fetch t.members where t.name = 'teamA'
// 실행된 SQL
SELECT T.*, M.* FROM TEAM T INNER JOIN MEMBER M ON T.ID=M.TEAM_ID WHERE T.NAME = '팀A'
teamname = 팀A, team = hellojpa.jpql.Team@4fe533ff
->username = 회원1, member = Member{id=1, username='회원1', age=10}
->username = 회원2, member = Member{id=2, username='회원2', age=10}
teamname = 팀A, team = hellojpa.jpql.Team@4fe533ff
->username = 회원1, member = Member{id=1, username='회원1', age=10}
->username = 회원2, member = Member{id=2, username='회원2', age=10}
- 팀과 회원 컬렉션을 함께 조회할 시 연관된 회원 컬렉션도 함께 조회한다.
페치 조인과 일반 조인의 차이
일반 조인
- JPQL은 결과를 반환할 때 연관관계를 고려하지 않는다.
- SELECT 절에 지정한 엔티티만 조회할 뿐이다.
- 일반 조인의 경우 팀 엔티티만 조회하고, 회원 엔티티는 조회하지 않는다.
페치 조인
- 페치 조인을 사용할 때만 연관된 엔티티도 함께 조회한다. (즉시 로딩)
- 페치 조인은 객체 그래프를 SQL 한 번에 조회하는 개념이다.
페치 조인의 특징과 한계
페치 조인의 특징
- 페치 조인을 사용하면 SQL 한 번으로 연관된 엔티티를 함께 조회한다. - 성능 최적화
- 페치 조인은 글로벌 로딩 전략보다 우선적으로 실행된다.
- 글로벌 로딩 전략 : @OneToMany(fetch = FetchType.XXX)
- 실무에서 글로벌 로딩 전략은 모두 지연 로딩이다.
- 최적화가 필요한 곳은 페치 조인을 적용한다.(N + 1 문제 해결 등)
페치 조인의 한계
- 페치 조인 대상에는 별칭을 줄 수 없다.
- 하이버네이트는 가능하지만 가급적 사용하지 않는 것이 좋다.
- 둘 이상의 컬렉션을 페치 할 수 없다.
- 컬렉션을 페치 조인하면 페이징 API를 사용할 수 없다.
- 페이징 API : setFirstResult, setMaxResults
- 일대일, 다대일 같은 단일 값 연관 필드들은 페치 조인해도 페이징이 가능하다.
- 하이버네이트는 경고 로그를 남기고 메모리에서 페이징 하므로 매우 위험하다.
페치 조인 정리
- 모든 것을 페치 조인으로 해결할 수는 없다.
- 페치 조인은 객체 그래프를 유지할 때 사용하면 효과적이다.
- 여러 테이블을 조인하여 엔티티가 가진 모양이 아닌 전혀 다른 결과를 내야 한다면 일반 조인을 사용한다.
- 필요한 데이터들만 조회하여 DTO로 반환하는 것이 효과적
반응형
'프레임워크(Framework) > JPA' 카테고리의 다른 글
[JPA] JPQL 엔티티 직접 사용 - 객체지향 쿼리 언어 JPQL (10) (0) | 2022.12.22 |
---|---|
[JPA] JPQL - 다형성 쿼리 - 객체지향 쿼리 언어 JPQL (9) (0) | 2022.12.21 |
[JPA] JPQL 경로 표현식 - 객체지향 쿼리 언어 JPQL (7) (0) | 2022.12.19 |
[JPA] JPQL 타입 표현과 기타식, 조건식 - 객체지향 쿼리 언어 JPQL (6) (0) | 2022.12.18 |
[JPA] JPQL 서브 쿼리 - 객체지향 쿼리 언어 JPQL (5) (0) | 2022.12.17 |