자바 ORM 표준 JPA 프로그래밍(인프런)

Ch11. 객체지향 쿼리 언어2(중급 문법) - 페치 조인(기본)

webmaster 2021. 12. 18. 12:00
728x90
  • SQL 조인 종류 X
  • JPQL에서 성능 최적화를 위해 제공하는 기능
  • 연관된 엔티티나 컬렉션을 SQL 한 번에 함께 조회하는 기능 
  • join fetch 명령어 사용 
  • 페치 조인 ::= [ LEFT [OUTER] | INNER ] JOIN FETCH 조인 경로
  • 회원을 조회하면서 연관된 팀도 함께 조회(SQL 한 번에)
  • SQL을 보면 회원뿐만 아니라 팀(T.*)도 함께 SELECT 
    • [JPQL] select m from Member m join fetch m.team 
    • [SQL] SELECT M.*, T.* FROM MEMBER M INNER JOIN TEAM T ON M.TEAM_ID=T.ID

페치 조인 사용하기

예시

Test

Team teamA = new Team();
teamA.setName("teamA");
em.persist(teamA);

Team teamB = new Team();
teamB.setName("teamB");
em.persist(teamB);

Member member1 = new Member();
member1.setUsername("회원1");
member1.changeTeam(teamA);
em.persist(member1);

Member member2 = new Member();
member2.setUsername("회원2");
member2.changeTeam(teamA);
em.persist(member2);

Member member3 = new Member();
member3.setUsername("회원3");
member3.changeTeam(teamB);
em.persist(member3);

em.flush();
em.clear();

String query = "select m "
        +" from Member m";
List<Member> result = em.createQuery(query, Member.class)
        .getResultList();
for(Member member : result) {
    System.out.println(member.getUsername() + ", " + member.getTeam().getName());
    //회원1, 팀A(SQL)
    //회원2, 팀A(1차캐시)
    //회원3, 팀B(SQL)
    //회원 100명 -> N+1 
    //fetch join 으로 해결할수 밖에 없다
}

실행 결과

Hibernate: 
    /* select
        m  
    from
        Member m */ select
            member0_.id as id1_0_,
            member0_.age as age2_0_,
            member0_.memberType as memberty3_0_,
            member0_.TEAM_ID as team_id5_0_,
            member0_.username as username4_0_ 
        from
            Member member0_
Hibernate: 
    select
        team0_.id as id1_3_0_,
        team0_.name as name2_3_0_ 
    from
        Team team0_ 
    where
        team0_.id=?
회원1, teamA
회원2, teamA
Hibernate: 
    select
        team0_.id as id1_3_0_,
        team0_.name as name2_3_0_ 
    from
        Team team0_ 
    where
        team0_.id=?
회원3, teamB

페치 조인 사용

Fetch Join 사용

실행 결과

페치 조인으로 한방쿼리가 나간다(LASY모드 이지만, 프록시가 아니다) -> N+1 문제 해결

  • FetchJoin을 사용하면 Lazy 모드지만, 한방 쿼리로 Join 해서 데이터를 가지고 온다.

컬렉션 페치 조인

  • 일대다 관계, 컬렉션 페치 조인

JPQL

select t
from Team t join fetch t.members
where t.name = ‘팀A

SQL

SELECT T.*, M.*
FROM TEAM T
INNER JOIN MEMBER M ON T.ID=M.TEAM_ID
WHERE T.NAME = '팀A'

일대다 페치조인을 하게 되면 데이터가 뻥튀기 된다.
같은 데이터를 가지고 오는 이유

  • 일대다 fetchJoin을 하게되면 다 쪽으로 데이터가 뻥튀기되는 문제가 발생한다.
  • 팀 A 에 회원 데이터가 2개 이므로 팀 A 데이터가 2개가 출력된다.

페치 조인과 DISTINCT

SQL의 DISTINCT는 중복된 결과를 제거하는 명령

JPQL의 DISTINCT 2가지 기능 제공 

1. SQL에 DISTINCT를 추가

select distinct t
from Team t join fetch t.members
where t.name = ‘팀A’

SQL에 DISTINCT를 적용해 준다 -> 여기서는 다른 데이터로 중복제거 실패

  • SQL에 DISTINCT를 추가하지만 데이터가 다르므로 SQL 결과에서 중복제거 실패

2. 애플리케이션에서 엔티티 중복 제거

어플리케이션에서

  • DISTINCT가 추가로 애플리케이션에서 중복 제거시도
  • 같은 식별자를 가진 Team 엔티티 제거
  • 위험하다 -> 중복된 데이터가 여러 개라면??? , 뒤에 나오지만 @BatchSize를 통해 해결 가능

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

일반 조인 실행 시 연관된 엔티티를 함께 조회하지 않음

일반 조인

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
WHERE T.NAME = '팀A'

페치 조인

JPQL

select t
from Team t join fetch t.members
where t.name = ‘팀A'

SQL

SELECT T.*, M.*
FROM TEAM T
INNER JOIN MEMBER M ON T.ID=M.TEAM_ID
WHERE T.NAME = '팀A
  • JPQL은 결과를 반환할 때 연관관계 고려 X 
  • 단지 SELECT 절에 지정한 엔티티만 조회할 뿐 
  • 여기서는 팀 엔티티만 조회하고, 회원 엔티티는 조회 X
  • 페치 조인을 사용할 때만 연관된 엔티티도 함께 조회(즉시 로딩) 
  • 페치 조인은 객체 그래프를 SQL 한 번에 조회하는 개념
  • 페치 조인은 연관된 엔티티를 함께 조회함

 

728x90