Free Lines Arrow
본문 바로가기
DataBase/JPA

[JPA] JPQL Fetch Join

by skahn1215 2021. 9. 26.
728x90
반응형

Fetch Join

  • 실무에서 너무 중요한 개념이기 때문에 반드시 잘 알아둬야 한다.
  • SQL 조인과는 다른개념이다.
  • JPQL 에서 성능 최적화를 위해 제공해 주는 기능이다.
  • join fetch 를 써준다.

 

 

어떻게 성능최적화가 될까?

JPQL 에서는 N+1 문제가 발생하는 경우가 있다.

N+1 문제는 1나의 쿼리를 수행했는데 그외에 또 쿼리가 나가는 문제이다.

 

 

 

언제 N + 1 문제가 발생하는가?

  • 연관관계 에서 회원이 팀 객체를 가지고 있다고 생각해보자.
  • 1. 회원을 조회 하는 쿼리를 수행한다.(1)
  • 2. 회원이 가지고있는 팀을 조회한다(N 회원수 만큼)
  • 이렇게 N+1 문제가 발생한다.

 

예제

  • Member 는 Lazy fetch 를 사용한다. 지연로딩
  • 그렇기 때문에 List<JPQLMember> 에 들어 있는 team은 proxy 객체이다.
  • 실제로 조회 할때 team 에대한 쿼리를 수행한다.
String query3 = "select m from JPQLMember m join m.team";
List<JPQLMember> result7 = em.createQuery(query3, JPQLMember.class)
        .getResultList();

for (JPQLMember member : result7) {
    System.out.println("result = "+ member.getUsername()+","+member.getTeam().getName());
}

 

결과

  • member를 조회하는 쿼리 수행(1번)
  • team을 조회하는 쿼리 수행(N번)
  • 실제로 member를 조회하는 쿼리 1번 
  • team을 조회하는 쿼리가 2번 나간다.
Hibernate: 
    /* select
        m 
    from
        JPQLMember m 
    join
        m.team */ select
            jpqlmember0_.MEMBER_ID as MEMBER_I1_1_,
            jpqlmember0_.age as age2_1_,
            jpqlmember0_.TEAM_ID as TEAM_ID5_1_,
            jpqlmember0_.type as type3_1_,
            jpqlmember0_.USER_NAME as USER_NAM4_1_ 
        from
            JPQLMember jpqlmember0_ 
        inner join
            JPQLTeam jpqlteam1_ 
                on jpqlmember0_.TEAM_ID=jpqlteam1_.TEAM_ID
=======================================================================================
Hibernate: 
    select
        jpqlteam0_.TEAM_ID as TEAM_ID1_3_0_,
        jpqlteam0_.name as name2_3_0_ 
    from
        JPQLTeam jpqlteam0_ 
    where
        jpqlteam0_.TEAM_ID=?
result = 회원1,teamA
result = 회원2,teamA
=======================================================================================
Hibernate: 
    select
        jpqlteam0_.TEAM_ID as TEAM_ID1_3_0_,
        jpqlteam0_.name as name2_3_0_ 
    from
        JPQLTeam jpqlteam0_ 
    where
        jpqlteam0_.TEAM_ID=?
result = 회원3,teamB

 

 

 

Fetch Join을 사용하여 한방에 조회하기

  • 이러한 N+1 문제를 해결해 주는 것이 fetch join이다.

 

예제

String query3 = "select m from JPQLMember m join fetch m.team";
List<JPQLMember> result7 = em.createQuery(query3, JPQLMember.class)
        .getResultList();

for (JPQLMember member : result7) {
    System.out.println("result = "+ member.getUsername()+","+member.getTeam().getName());
}

 

결과

  • 조회 결과는 동일하지만 쿼리는 한번만 발생했다.
Hibernate: 
    /* select
        m 
    from
        JPQLMember m 
    join
        fetch m.team */ select
            jpqlmember0_.MEMBER_ID as MEMBER_I1_1_0_,
            jpqlteam1_.TEAM_ID as TEAM_ID1_3_1_,
            jpqlmember0_.age as age2_1_0_,
            jpqlmember0_.TEAM_ID as TEAM_ID5_1_0_,
            jpqlmember0_.type as type3_1_0_,
            jpqlmember0_.USER_NAME as USER_NAM4_1_0_,
            jpqlteam1_.name as name2_3_1_ 
        from
            JPQLMember jpqlmember0_ 
        inner join
            JPQLTeam jpqlteam1_ 
                on jpqlmember0_.TEAM_ID=jpqlteam1_.TEAM_ID



result = 회원1,teamA
result = 회원2,teamA
result = 회원3,teamB

 

 

 

페치 조인과 DISTINCT

  • 위에서 Member 를 생성하고 Member객체에 Team을 셋팅해주었다.
  • 그럼 다음과 같이 표현 할 수 있다.

 

 

 

조회

  • 그럼 TeamA 만 조회한다면 어떻게 될까?
  • 우리가 기대하는 것은 TeamA 하나만 조회 되어야 하는데
  • TeamA 에는 member1 과 member2 가 있으므로 
  • 다음과 같은 결과가 발생한다.

 

예제

String fetchJoinQuery2 = "select  t from JPQLTeam t join fetch t.members where t.name = 'teamA'";
List<JPQLTeam> fetchJoinResult2 = em.createQuery(fetchJoinQuery2, JPQLTeam.class)
        .getResultList();

for (JPQLTeam team : fetchJoinResult2) {
    System.out.println("fetchJoinResult2= " + team.getName() + "," + team.getMembers().size());
    //회원1, 팀A(SQL)
    //회원2, 팀A(1차캐시)
}

 

결과

실제로 2번 조회가 되었다.

fetchJoinResult2= teamA,2
fetchJoinResult2= teamA,2

 

 

DISTINCT 를 이용하여 중복 없애기

  • DISTINCT 를 앞에 써주면된다.
String fetchJoinQuery2 = "select DISTINCT t from JPQLTeam t join fetch t.members where t.name = 'teamA'";
List<JPQLTeam> fetchJoinResult2 = em.createQuery(fetchJoinQuery2, JPQLTeam.class)
        .getResultList();

for (JPQLTeam team : fetchJoinResult2) {
    System.out.println("fetchJoinResult2= " + team.getName() + "," + team.getMembers().size());
    //회원1, 팀A(SQL)
    //회원2, 팀A(1차캐시)
}

 

 

 

의문점?

  • 보통 DISINCT 는 모든 컬럼 내용이 같아야 중복이 없어진다.
  • 그렇다면 위 예제에서는 Member 회원 이름도 다르고 회원의 ID 도 다르다
  • 그렇게 되면 똑같이 2개의 데이터가 조회 되어야 한다.

 

 

어떻게 하나의 데이터만 나오는가?

JPA 에서는 영속성컨텍스트를 확인하고 같은 식별자를 가진 엔티티를 제거해준다.

그렇기 때문에 하나의 데이터만 나올수 있는것이다.

 

그림설명

 

 

 

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

  • 일반 조인의 경우 연관된 엔티티를 함께 조회하지 않는다.
  • 하지만 페치조인을 쓰게 되면 연관된 엔티티도 한방에 조회한다.

 

 

페치조인의 한계

  • 페치조인 대상에는 별칭을 줄 수 없다.
  • 페치조인의 목적은 나랑 연관된 것을 다 끌고 오겠다는 것이다.
    - 별칭을 줘서 where 조건을 쓰면 안된다.
  • 둘이상의 컬레션은 페치조인 할수 없다.
  • 컬렉션을 페치 조인하면 페이징 API(setFirstResult, setMaxResults)를 사용할수 없다.
  • 일대일, 다대일 같은 단일 값 연관 필드들은 페치 조인해도 페이징이 가능하다
    - 데이터 뻥튀기가 안되기 때문이다.
  • 최적화가 필요한 곳은 페치 조인 적용한다.

 

728x90
반응형

'DataBase > JPA' 카테고리의 다른 글

[JPA] JPQL Named 쿼리  (0) 2021.09.28
[JPA] JPQL 엔티티 직접 사용  (0) 2021.09.28
[JPA] JPQL 경로표현식  (0) 2021.09.26
[JPA] JPQL 기본함수, 사용자 정의 함수 호출  (0) 2021.09.25
[JPA] JPQL CASE  (0) 2021.09.25

댓글