"인프런 - 자바 ORM 표준 JPA 프로그래밍 강의를 듣고 작성한 글 입니다."
www.inflearn.com/course/ORM-JPA-Basic#
자바 ORM 표준 JPA 프로그래밍 - 기본편 - 인프런
JPA를 처음 접하거나, 실무에서 JPA를 사용하지만 기본 이론이 부족하신 분들이 JPA의 기본 이론을 탄탄하게 학습해서 초보자도 실무에서 자신있게 JPA를 사용할 수 있습니다. 초급 웹 개발 프로그
www.inflearn.com
컬렉션 페치 조인에서 페이징 활용?
앞에서 작성한 글에는 컬렉션 페치 조인시 페이징을 활용하지 마라고 했지만, 그럼에도 불구하고 페이징을 사용해야 한다면 일단 아래의 코드를 살펴보자.
- JpaMain.java
String query = "select t from Team t"; // 일단은 페치 조인을 사용하지 않는다.
List<Team> result = em.createQuery(query, Team.class)
.setFirstResult(0)
.setMaxResults(2)
.getResultList();
System.out.println("result = " + result.size());
for (Team team : result){
System.out.println("team = " + team.getName() + "| members = " + team.getMembers().size());
for (Member member : team.getMembers()){
System.out.println("-> member = " + member);
}
}
- Hibernate SQL
Hibernate: /*현재 있는 팀 데이터 2개 호출(팀A, 팀B)*/
/* select
t
from
Team t */ select
team0_.id as id1_3_,
team0_.name as name2_3_
from
Team team0_ limit ? /*페이징 쿼리가 정상적으로 출력되었다.*/
result = 2
Hibernate:
/*팀A 멤버 검색 - for loop 로 인한 LAZY 로딩*/
select
members0_.TEAM_ID as team_id5_0_0_,
members0_.id as id1_0_0_,
members0_.id as id1_0_1_,
members0_.age as age2_0_1_,
members0_.TEAM_ID as team_id5_0_1_,
members0_.type as type3_0_1_,
members0_.username as username4_0_1_
from
Member members0_
where
members0_.TEAM_ID=?
team = 팀A| members = 2
-> member = Member{id=3, username='회원1', age=0}
-> member = Member{id=4, username='회원2', age=0}
Hibernate:
/*팀B 멤버 검색 - for loop 로 인한 LAZY 로딩*/
select
members0_.TEAM_ID as team_id5_0_0_,
members0_.id as id1_0_0_,
members0_.id as id1_0_1_,
members0_.age as age2_0_1_,
members0_.TEAM_ID as team_id5_0_1_,
members0_.type as type3_0_1_,
members0_.username as username4_0_1_
from
Member members0_
where
members0_.TEAM_ID=?
team = 팀B| members = 1
-> member = Member{id=5, username='회원3', age=0}
위와 같은 형식으로 페이징 기능을 사용한다면 팀의 갯수에 따라 연관된 멤버들을 조회하는 쿼리의 갯수가 더 늘어나게 되어 결국 성능이 떨어지게 될 것이다.
그렇기 때문에 페치 조인을 사용하면 좋은데, 페이징을 사용하면 결국 페치 조인을 사용할 수 없게 된다.
이럴 경우 다음과 같은 해결방법이 있다.
- Team.java
@BatchSize(size = 100)
@OneToMany(mappedBy = "team")
private List<Member> members = new ArrayList<>();
일대다 연관관계에서 위와 같이 컬렉션 선언부에 @BatchSize 라는 어노테이션을 주고 size 값을 적당히 크게 잡아준다.
이후 다시 애플리케이션을 실행해보면 어떤 결과가 돌아올까?
- Hibernate SQL
Hibernate:
/* select
t
from
Team t */ select
team0_.id as id1_3_,
team0_.name as name2_3_
from
Team team0_ limit ?
result = 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 = 팀A| members = 2
-> member = Member{id=3, username='회원1', age=0}
-> member = Member{id=4, username='회원2', age=0}
team = 팀B| members = 1
-> member = Member{id=5, username='회원3', age=0}
위와 같이 @BatchSize 어노테이션을 사용하면 연관된 Entity 를 조회할 때 지정된 size 만큼 SQL 의 in 절을 사용해서 조회하게 된다.
이는 즉시 로딩이므로 Team 을 조회하는 시점에 Member 를 같이 조회한다.
@BatchSize 어노테이션으로 인해 Team 의 갯수만큼 추가 SQL 을 생성하지 않고, 조회한 Team 의 id 들을 모아서 SQL in 절을 DB 에 전달한다.
여기서 size 는 in 절에 올 수 있는 최대 인자 갯수를 말하며, 만약 Team 의 갯수가 150 개인 경우 in 절이 팀 id 갯수 100개로 한번, 50개로 한번 실행될 것이다.
@BatchSize 어노테이션과 같은 기능의 경우 아예 글로벌 옵션으로 지정해 줄 수도 있다.
- persistence.xml
<property name="hibernate.default_batch_fetch_size" value="100"/>
결과적으로 위와 같은 기능을 활용하면 쿼리가 N + 1 이 아니라, 딱 테이블 갯수만큼 맞춰 줄 수 있다.
페치 조인의 특징
- 연관된 Entity 들을 SQL 한 번으로 조회하여 성능 최적화를 할 수 있다.
- Entity 에 직접 적용하는 글로벌 로딩 전략보다 우선한다.
* OneToMany(fetch = FetchType.LAZY) // 글로벌 로딩 전략
- 실무에서 글로벌 로딩 전략은 모두 지연 로딩이다.
- 최적화가 필요한 곳은 페치 조인을 적용한다.
페치 조인 정리
- 모든 것을 페치 조인으로 해결할 수는 없다.
- 페치 조인은 객체 그래프를 유지할 때 사용하면 효과적이다.
- 여러 테이블을 조인해서 Entity 가 가진 모양이 아닌 전혀 다른 결과를 내야 하면, 페치 조인 보다는 일반 조인을 사용하고 필요한 데이터들만 조회해서 DTO 로 반환하는 것이 효과적이다.(중요)
'JPA' 카테고리의 다른 글
JPQL - JPQL 의 여러가지 기능들(2) (0) | 2020.11.29 |
---|---|
JPQL - JPQL 의 여러가지 기능들(1) (0) | 2020.11.29 |
JPQL - JPQL 페치 조인(3) (0) | 2020.11.28 |
JPQL - JPQL 페치 조인(2) (0) | 2020.11.28 |
JPQL - JPQL 페치 조인(1) (0) | 2020.11.28 |