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

JPQL - JPQL 페치 조인(3)

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

"인프런 - 자바 ORM 표준 JPA 프로그래밍 강의를 듣고 작성한 글 입니다."

www.inflearn.com/course/ORM-JPA-Basic#

 

자바 ORM 표준 JPA 프로그래밍 - 기본편 - 인프런

JPA를 처음 접하거나, 실무에서 JPA를 사용하지만 기본 이론이 부족하신 분들이 JPA의 기본 이론을 탄탄하게 학습해서 초보자도 실무에서 자신있게 JPA를 사용할 수 있습니다. 초급 웹 개발 프로그

www.inflearn.com

 

 

페치 조인과 일반 조인의 차이점?

- 일반 조인은 실행 시 연관된 Entity 를 함께 조회하지 않는다.

: JPQL 은 결과를 반환할 때 연관관계를 고려하지 않는다, 단지 SELECT 절에 지정한 Entity 만 조회할 뿐이다.

여기서는 Team Entity 만 조회하고, 회원 Entity 는 조회하지 않는다.

- JPQL : select t from Team t join t.members m where t.name = '팀A'

- SQL : SELECT T.* FROM TEAM T INNER JOIN MEMBER M ON T.ID = M.TEAM_ID

 

- JpaMain.java

String query = "select t from Team t join t.members m";

List<Team> result = em.createQuery(query, Team.class)
	.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: 
    /* select
        t 
    from
        Team t 
    join
        t.members m */ select
            team0_.id as id1_3_,
            team0_.name as name2_3_  /* 일반 조인으로 실행되었기 때문에 연관된 Entity 를 함께 조회하지 않고 Team 객체 데이터만 조회했다.*/
        from
            Team team0_ 
        inner join
            Member members1_ 
                on team0_.id=members1_.TEAM_ID
result = 3
Hibernate: 
    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}
team = 팀A| members = 2
-> member = Member{id=3, username='회원1', age=0}
-> member = Member{id=4, username='회원2', age=0}
Hibernate: 
    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}

 

* 페치 조인을 사용할 때만 연관된 Entity 도 함께 조회한다.(즉시 로딩)

* 페치 조인은 객체 그래프를 SQL 한번에 조회하는 개념이다.

 

 

페치 조인의 특징과 한계

- 페치 조인 대상에는 원칙적으로 별칭을 줄 수 없다.

String query = "select t from Team t join fetch t.members as m"; // 이와 같은 별칭 부여는 원칙적으로 불가능하다.

자세히 말하자면, 별칭을 준 후 그 별칭을 통해 객체 그래프 탐색등을 해서는 안된다는 뜻이다.

왜 그럴까?

페치 조인이라는 건 기본적으로 연관된 객체 데이터를 다 긁어 오는 것이다. 중간에 조건을 걸어서 필터링 할 수 없다.

만약 전체 데이터에서 필터링을 통해 조건에 맞는 데이터를 가져오고 싶은 경우엔 페치 조인을 사용해서는 안된다.

(따로 쿼리를 만들어서 조회해야 한다.)

 

그렇다면 왜 따로 조회해야 할까?

Team 과 연관된 회원이 5명인데, 그 중 한명만 불러올 경우 여기서 조작을 잘못하게 되면 나머지 4명의 데이터가 누락되어 있기 때문에 예상과 달리 이상하게 동작할 수 있다.

 

위와 같은 이유들 때문에 페치 조인은 별칭을 주지 않는 것이 관례이다.

Hibernate 는 가능하나, 가급적 사용하지 않는 걸 추천한다.

 

 

- 둘 이상의 컬렉션은 페치 조인 할 수 없다.

둘 이상의 컬렉션을 페치 조인하게 되면 안 그래도 일반 페치 조인만으로 데이터가 부풀려질 수 있는데 페치 조인을 한번 더 하는 것으로 연관관계가 사실상 일대다대다 의 형식이 되어서 데이터가 정말 예상치 못한 방향으로 이상하게 튈 수 있다.

 

 

- 컬렉션을 페치 조인하면 페이징 API 를 사용할 수 없다.

데이터가 부풀려지는 상황에서 페이징 API 를 통해 필터링을 걸게 된다면 정확한 데이터의 집계가 되지 않게 된다.

예를 들어 본래 2개의 같은 값을 가지는 필드들이 있는데, 페이징의 기준 사이즈를 1로 잡아버리면 둘 중 하나만 검출되게 되어, 본래는 2개의 필드가 있음에도 불구하고 데이터가 하나만 존재하는 것처럼 결과가 반환되는 것이다.

 

일대일, 다대일 같은 단일 값 연관 필드들은 페치 조인해도 페이징이 가능하지만, 일대다 와 같은 컬렉션 페치 조인의 경우 Hibernate 는 경고 로그를 남기고 메모리에서 페이징을 하나 매우 위험하다.

 

- JpaMain.java

String query = "select t from Team t join fetch t.members m";

// 페치 조인에서 페이징 API 를 사용하는 경우 문제점
List<Team> result = em.createQuery(query, Team.class)
	.setFirstResult(0)
	.setMaxResults(1)
	.getResultList();

 - Hibernate SQL

WARN: HHH000104: firstResult/maxResults specified with collection fetch; applying in memory!
Hibernate: 
    /* select
        t 
    from
        Team t 
    join
        fetch t.members m */ 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

 

위의 Hibernate SQL 에서 경고 문구 이후 생성된 SQL 문을 보면 페이징 API 를 사용하였음에도 불구하고 페이징을 하는 쿼리가 존재하지 않는다.

즉, DB 에서 Team 에 관한 데이터들을 페이징과 상관없이 모두 가져온 것이다.

이렇게 되면 딱 장애 나기 좋다.

그냥 페치 조인을 하는 경우 페이징은 절대로 하면 안된다.

 

 

 

그럼에도 불구하고 어떻게든 페이징을 사용해야 한다면 다음 번 글에서 컬렉션 페치 조인시 페이징을 하는 방법을 알아보자.

 

'JPA' 카테고리의 다른 글

JPQL - JPQL 의 여러가지 기능들(1)  (0) 2020.11.29
JPQL - JPQL 페치 조인(4)  (0) 2020.11.28
JPQL - JPQL 페치 조인(2)  (0) 2020.11.28
JPQL - JPQL 페치 조인(1)  (0) 2020.11.28
JPQL - 경로 표현식  (0) 2020.11.28