본문 바로가기
  • 개발공부 및 일상적인 내용을 작성하는 블로그 입니다.
JPA

JPQL - JPQL 페치 조인(4)

by 방구석 대학생 2020. 11. 28.

"인프런 - 자바 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