fetch join을 설명하기전에 fetch type에 대해 복습을 해보자!
# FetchType
@ManyToOne, @OneToOne
FetchType이 기본이 EAGER다
- FetchType.EAGER
entity를 조회할 때 연관된 entity도 같이 조회해온다
이 경우 연관된 entity까지 select하는 쿼리가 나가야 하므로 성능이 좋지 않다
ex. member entity 조회 시 team entity까지 select 하므로 2번의 select 쿼리가 나간다
그래서 성능을 위해 모든 엔티티의 연관관계의 FetchType은 LAZY로 설정해두는 것이 좋다
- FetchType.LAZY
조회한 엔티티와 연관된 엔티티는 실제 사용 시에 조회해온다
ex. member 조회 -> member만 select
member.getTeam().getName() -> 이때 team select
# fetch join
모든 엔티티를 FetchType.LAZY로 설정해두면 연관된 엔티티를 조회해오지 않는다고 했다
그러면 연관된 엔티티를 같이 조회해와야하는 경우 어떻게 해야 할까?
JPQL에서 연관된 엔티티(또는 컬렉션)를 SQL 한번으로 함께 조회하려면 fetch join을 사용하면 된다
[문법 - join fetch]
[예제 코드]
String query = "select m from Member m join fetch m.team";
List<Member> result = em.createQuery(query, Member.class).getResultList();
for (Member member : result) {
System.out.println("member = " + member.getUsername() +" , " + member.getTeam().getName());
}
[실제 생성되는 SQL]
select
member0_.id as id1_0_0_,
team1_.id as id1_3_1_,
member0_.age as age2_0_0_,
member0_.TEAM_ID as team_id5_0_0_,
member0_.type as type3_0_0_,
member0_.username as username4_0_0_,
team1_.name as name2_3_1_
from
Member member0_
inner join
Team team1_
on member0_.TEAM_ID=team1_.id
# fetch join 한계
1. fetch join 대상에는 별칭을 주지 말자! 연관된 엔티티 일부만 필터링해서 가져오는 것은 바람직한 사용법이 아님
2. 일대다 조인의 경우 연관된 엔티티를 모두 가져오므로 데이터 뻥튀기가 생긴다
ex. select t from Team t join fetch t.members;
팀A-멤버1,멤버2
팀A-멤버1,멤버2
팀B-멤버3
💡 distinct를 사용해서 해결
1. SQL에 distinct 추가
2. 애플리케이션에서 엔티티 중복 제거
[예제 코드]
String distinctQuery = "select distinct t from Team t join fetch t.members";
List<Team> distinctList = em.createQuery(distinctQuery, Team.class).getResultList();
[실제 생성되는 SQL]
select
distinct team0_.id as id1_3_0_,
members1_.id as id1_0_1_,
team0_.name as name2_3_0_,
members1_.age as age2_0_1_,
members1_.TEAM_ID as team_id5_0_1_,
members1_.type as type3_0_1_,
members1_.username as username4_0_1_,
members1_.TEAM_ID as team_id5_0_0__,
members1_.id as id1_0_0__
from
Team team0_
inner join
Member members1_
on team0_.id=members1_.TEAM_ID
2. 컬렉션을 fetch join 하면 페이징 API(setFirstResult, setMaxResults)를 사용할 수 없다
일대다, 다대다 연관관계에서 fetch join을 하면 데이터가 뻥튀기 되고 페이징 API를 사용할 수 없다
[예제 코드]
String teamQuery = "select t from Team t join fetch t.members";
List<Team> teamList = em.createQuery(teamQuery, Team.class)
.setFirstResult(0)
.setMaxResults(2)
.getResultList();
[경고]
WARN: HHH000104: firstResult/maxResults specified with collection fetch; applying in memory!
[실제 생성 되는 SQL]
select
team0_.id as id1_3_0_,
members1_.id as id1_0_1_,
team0_.name as name2_3_0_,
members1_.age as age2_0_1_,
members1_.TEAM_ID as team_id5_0_1_,
members1_.type as type3_0_1_,
members1_.username as username4_0_1_,
members1_.TEAM_ID as team_id5_0_0__,
members1_.id as id1_0_0__
from
Team team0_
inner join
Member members1_
on team0_.id=members1_.TEAM_ID
=> 페이징 관련 구문을 찾아볼 수 없다
이는 SQL로 모든 데이터를 조회해온 후에 애플리케이션 메모리에서 페이징을 하기 때문이다
성능에 매우 안 좋으므로 이렇게 하지 말자!
💡해결 방안
fetch join을 하지 않고, BatchSize 사용 - IN 쿼리를 만들어서 한번엔 연관된 엔티티도 조회해옴, 페이징 가능
1. @BatchSize 어노테이션 사용
@BatchSize(size = 100)
@OneToMany(mappedBy = "team")
private List<Member> members = new ArrayList<>();
2. 글로벌 설정으로 batch size 설정(persistance.xml)
<property name="hibernate.default_batch_fetch_size" value="100"/>
[예제 코드]
String teamQuery = "select t from Team t";
List<Team> teamList = em.createQuery(teamQuery, Team.class)
.setFirstResult(0)
.setMaxResults(2)
.getResultList();
[실제 생성되는 SQL]
/* load one-to-many jpql.Team.members */
select
members0_.TEAM_ID as team_id5_0_1_,
members0_.id as id1_0_1_,
members0_.id as id1_0_0_,
members0_.age as age2_0_0_,
members0_.TEAM_ID as team_id5_0_0_,
members0_.type as type3_0_0_,
members0_.username as username4_0_0_
from
Member members0_
where
members0_.TEAM_ID in (
?, ?
)
[참고] fetch와 관련하여 더 자세한 내용은 하이버네이트 공식문서 11. Fetching 을 읽어보시길 추천!
https://docs.jboss.org/hibernate/orm/6.0/userguide/html_single/Hibernate_User_Guide.html#fetching
인프런 - 자바 ORM 표준 JPA 프로그래밍 - 기본편(김영한 강사님) 강의를 수강하며 정리한 글입니다 :)
'개발 > JPA' 카테고리의 다른 글
[JPA] 지연로딩과 조회 성능 최적화 2 - fetch join, DTO로 조회 (0) | 2022.04.19 |
---|---|
[JPA] 지연로딩과 조회 성능 최적화 1 - xToOne (0) | 2022.04.18 |
[JPA] Hibernate Query Language(HQL) Projection / 하이버네이트 쿼리 언어 프로젝션 조회 / 특정 컬럼만 조회 / 예문 (0) | 2022.04.11 |
[JPA] 연관관계 영속성 전이 CASCADE (0) | 2022.04.07 |
[JPA] 지연 로딩 LAZY , 즉시 로딩 EAGER (0) | 2022.04.06 |
댓글