Notice
Recent Posts
Recent Comments
Link
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
Tags
- 그리디
- 자바
- 스프링 핵심 원리
- 백준
- SpringBoot
- 스프링
- Android
- http
- pointcut
- JDBC
- Proxy
- JPQL
- Exception
- db
- spring
- 인프런
- AOP
- Thymeleaf
- springdatajpa
- jpa
- 알고리즘
- java
- Greedy
- 스프링 핵심 기능
- kotlin
- QueryDSL
- 김영한
- Servlet
- transaction
- Spring Boot
Archives
- Today
- Total
개발자되기 프로젝트
Fetch Join2 본문
1. 페치 조인의 특징과 한계
- 페시 조인 대상에는 별칭을 줄 수 없다.
- 하이버네이트는 가능, 하지만 가급적 사용하지 말자.
- fetch join은 나랑 연관된 엔티티를 전부 다 가져옴.
따라서 fetch join에 별칭을 적용하여 탐색 시, 일부만 불러오게 됨.
예를들어 원래 t.members하면 5명이 나와야 하는데
fetch join에 별칭 적용하여 조건을 추가하여 3개가 나왔다고하자.
그러면 이 시점 이후에 t.members를 하면 5명이 아닌 3명만 조회된다.
하지만 JPA 기본 설계는 t.members하면 멤버 전체를 갈 수 있어야 한다.
(객체 그래프는 기본적으로 전부 조회할 수 있어야 함)
- 둘 이상의 컬렉션은 페치 조인할 수 없다.(일 - 다 - 다)
- 일대다도 데이터 뻥튀기인데 일대다대다는? 난리남 ㅋㅋㅋㅋ
- 컬렉션을 페지 조인하면 페이징 API(setFirstResult, setMaxResult)를 사용할 수 없다.
- 일대일, 다대일 같은 단일 값 연관 필드들은 페치 조인해도 페이징 가능
- 데이터 뻥튀기가 안되기 때문!
- 일대다인 경우 fetch join후 페이징 하여 일부만 가져오게되면 팀A의 멤버는 회원1만 있는 것처럼 됨.
- 이렇게되면 망함 ㅋㅋㅋㅋㅋ
- 하이버네이트는 경고 로그를 남기고 메모리에서 페이징(매우 위험?~!!)
-
List<Team> resultList = em.createQuery(query, Team.class) .setFirstResult(0).setMaxResults(1).getResultList();
- 20:29:25.559 [main] WARN org.hibernate.hql.internal.ast.QueryTranslatorImpl - HHH000104:
firstResult/maxResults specified with collection fetch; applying in memory! - 어? 로그는 남고 쿼리에는 페이징이 없다. 싹다 가져왔다.
Hibernate: /* select distinct t From Team t join fetch t.members as m */ select distinct team0_.TEAM_ID as team_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_.TEAM_ID=members1_.TEAM_ID resultList.size() = 1 team.getName() = 팀A team.getMembers().size() : 2 --> member = Member{id=3, username='회원1', age=0} --> member = Member{id=4, username='회원2', age=0}
- 하지만 메모리에 퍼올린 다음에 페이징 처리됨.ㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋ노답
- 어떻게 해결? 일대다 --> 다대일로 방향을 바꿔
- 그래도 컬렉션의 경우 페이징 하고싶어? --> @BatchSize 적용.
-
- 일대일, 다대일 같은 단일 값 연관 필드들은 페치 조인해도 페이징 가능
2. @BatchSize
- N+1 이슈는 대부분 fetch join을 통해 해결이 가능
- 하지만 컬렉션의 경우 페이징 처리가 필요하나 fetch join사용시 페이징 API 사용에 문제 있음
- 이를 해결하기 위해 @BatchSize 적용 필요.
@Entity
public class Team {
@BatchSize(size = 100)
@OneToMany(mappedBy = "team")
private List<Member> members = new ArrayList<>();
}
- join fetch 사용 안하고 한 SQL을 통해 연관된 엔티티를 불러오며 페이징 처리가 필요.
String query = "select distinct t From Team t";
List<Team> resultList = em.createQuery(query, Team.class)
.setFirstResult(0).setMaxResults(1)
.getResultList();
- 사이즈는 예상 대로 팀a, 팀b로 2 이다.
- 하지만 멤버를 select할 때 m.TEAM_ID에 (? , ?) 물음포 두개가 있는데,
이 말은 team id 두 개(팀a, 팀b)가 들어가있는 것. - 즉 한 번에 팀에이 팀비와 연관된 멤버를 모두 다 가져온 것.
- 팀을 가져올 때 멤버는 레이지 로딩이나. 멤버를 가지로 올 때
- 해당 멤버의 팀 뿐만 아니라 리스트에 있는 팀을 인쿼리로 batch size로 지정한 만큼 내에서 존재하는 만큼 넘김.
- 지금은 팀이 두 개 뿐이이 물음표가 두개 날라감 ㅋㅋㅋㅋ
resultList.size() = 2
Hibernate:
/* 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 (
?, ?
)
team.getName() = 팀A team.getMembers().size() : 2
--> member = Member{id=3, username='회원1', age=0}
--> member = Member{id=4, username='회원2', age=0}
team.getName() = 팀B team.getMembers().size() : 1
--> member = Member{id=5, username='회원3', age=0}
- @BatchSize를 global setting으로 설정이 가능
-
<property name="hibernate.default_batch_fetch_size" value="100" />
- 즉 @BatchSize를 활용하면 N+1 이슈 해결.
- 쿼리가 N+1이 아니라 딱 테이블 수 만큼 맞출 수 있음.
3. 페치 조인의 특징과 한계
- 연관된 엔티티들을 SQL 한 번으로 조회 - 성능 최적화
- 엔티티에 직접 적용하는 글로벌 로딩 전략보다 우선함
- @OneToMany(fetch = FetchType.LAZY) 보다 우선순위 높음
- 실무에서 글로벌 로딩 전략을 모두 지연 로딩
- 최적화가 필요한 곳은 페치 조인 적용. -->대부분 최적화됨 ㅋㅋㅋ
4. 정리
- 모든 것을 페치 조인으로 해결할 수는 없음
- 페지 조인은 객체 그래프를 유지할 때 사용하면 효과적임
- m.team와 같이 찾아갈 결우 효과적.
- 여러 테이블은 조인해서 엔티티가 가진 모양이 아닌 전혀 다른 결과를 내야 하면,
- 페치 조인 보다는 일반적인 조인을 사용하고, 필요한 데이터들만 조회해서
- DTO로 반환하는 것이 효과적.
5. GitHub : 210816 FETCH JOIN2, @BatchSize
'인프런 > [인프런] 자바ORM 표준 JPA 프로그래밍' 카테고리의 다른 글
[JPQL] 엔티티 직접 사용 (0) | 2021.08.16 |
---|---|
다형성 쿼리 (0) | 2021.08.16 |
FETCH JOIN (0) | 2021.08.16 |
경로 표현식 (0) | 2021.08.16 |
JPQL 기본 함수 (0) | 2021.08.16 |
Comments