JPQL: fetch join - 컬렉션 페치 조인, 페치 조인 특징과 한계, 페이징

2022. 4. 4. 19:00JPA 기초

컬렉션 페치 조인(데이터 뻥튀기 조심하라

일대다 관계를 가정해보자. 현재 팀A에 멤버가 두명 속해있다. 이때 테이블을 조인하면 테이블이 아래와 같아진다.
따라서 DB에서 결과가 두줄이 날라온다...

SQL을 아는 사람이라면 DISTINCT 요소로 중복을 제거할 수 있을 것이라는 생각이 들 것이다. 근데 SQL의 DISTINCT는 컬럼의 모든 값이 일치해야 중복으로 처리되어 제거된다. 이 경우 Member의 Id와 Name이 다르다.
하지만 JPQL의 DISTINCT는 추가 기능을 제공한다.  SQL에 DISTINCT를 추가할 뿐만 아니라 애플리케이션에서 엔티티 중복을 제거한다.  따라서 JPQL에서는 같은 식별자를 가진 Team 엔티티를 퍼올릴때 중복을 제거한다.

데이터 뻥튀기가 되어 같은 데이터가 두 건 나오는 모습


페치 조인과 일반 조인의 차이
"select t from Team t join t.members m"
이 쿼리문은 당연히 team과 member의 조인쿼리가 발생한다. 하지만 페치 조인과의 차이점은 일반 조인은 select 대상이 team으로만 한정된다는 것이다. 실제 데이터를 퍼올리는 것은 team 뿐이다.
하지만 페치 조인은 연관된 엔티티를 함께 조회한다!!!!!!


페치 조인의 특징과 한계

  • 페치 조인 대상에는 별칭을 사용해서는 아니된다.
    만약 팀에 멤버가 5명인데, 이 중 3명만을 조회하고 싶은 경우는 team와 member를 조인하는 방식이 아니라 member만을 조회하는 쿼리를 새로 짜야한다. 객체 그래프를 탐색할 때, jpa연관관계는 team에서 member로 갈때 모든 members에 다 접근할 수 있어야 한다. 
select t from Team t join fetch t.members m where m.age > 10 
와 같은 쿼리는 금지다.

DB와 Entity 데이터의 일관성이 깨지는 이슈가 발생하기 때문이다.

  • 둘 이상의 컬렉션은 페치조인 할 수 없다. 앞서 설명했듯 일대 다 관계는 데이터 뻥튀기가 발생한다. 컬렉션이 두개 이상인 경우, 일대 다대 다니까.. 뻥튀기가 엄청 커질것이다..
  • 컬렉션을 페치조인하면 페이징API를 사용할 수 없다. 일대일, 다대일 같은 단일 값 연관 필드들은 페치조인해도 페이징 가능하다. 데이터 뻥튀기 걱정이 없으니까!! 근데 데이터 뻥튀기가 되는 상황에서 페이징이 가능할까..
    위의 그림에서
    팀A로 조회한 결과(다대일) DB는 두개의 데이터를 보냈다(뻥튀기 발생!!). 만약 페이지 사이즈를 1로 설정해 한개의 데이터만 받도록 설정한다면 2개의 데이터는 절반으로 잘려서, 팀A는 회원1만 가지고 있다는 식으로 정리된다. 이 경우 hibernate는 경고를 남기고 메모리에서 페이징 작업을 수행한다.

페이징을 원한다면

select t from team t join fetch t.members m

의 일대 다 쿼리를

select m from member m join fetch m.team t


처럼 다대 일로 뒤집어서 사용하면 된다. 아니면 batch size를 지정하는 방식도 있다.

BatchSize를 지정하면 현재 LAZY Loading(지연 로딩) 상태일때, member가 필요해서 호출하는 시점에 하나씩 쿼리를 날리는 것이 아니라, 100개를 한번에 가져온다. (1+N의 문제 해결이 가능하다)