JPA : Fetch Join

    Fetch Join이란? 쉽게 말해 한방 쿼리!


    JPQL에서는 Fetch Join이라는 쿼리 명령어를 지원해준다. Fetch Join은 찾고자 하는 대상과 연관된 엔티티와 컬렉션을 SQL 한 번에 함께 조회하는 기능이다. 기본적으로는 여러 개의 엔티티를 한꺼번에 조회했을 때 발생할 수 있는 N+1 문제를 해결하는데 큰 역할을 한다.

     

    FETCH JOIN 쿼리와 실제 SQL 쿼리 비교


    String query = "SELECT m FROM MEMBER m JOIN FETCH m.team";
    // SELECT m FROM MEMBER m LEFT [INNER/OUTER] JOIN FETCH m.team

    JPQL의 FETCH JOIN은 위와 같은 형태로 선언한다. 더 자세한 형태는 주석처리 된 부분에 표시되어있다. 기본적으로는 MEMBER Table을 불러오는데, 이 때 Member와 연관된 Team들을 같이 불러와서 LEFT INNER 혹은 OUTER Join해서 가져온다는 것이다. 즉, Member와 연관된 Team을 한번에 같이 불러온다. 이를 SQL 코드로 변경해보면 아래와 같다.

    SELECT
    M*,T* 
    FROM MEMBER M
    INNER JOIN TEAM T ON M.TEAM_ID = T.ID

    그렇다면 실제로 다음 쿼리가 나가는지를 한번 인텔리제이에서 확인해봤다. 

    인텔리제이에서도 동일하게 INNER JOIN으로 TEAM을 한번에 가져오는 것을 볼 수가 있다. 즉, 한방쿼리가 나가는 것을 볼 수 있다. 

     

    FETCH JOIN의 TABLE 관점 / 엔티티 관점


    위에서 FETCH JOIN은 쉽게 말해 LEFT를 기준으로 INNER 혹은 OUTER JOIN해서 연관된 엔티티를 한방에 가져오는 것이라고 했다. 그렇다면 FETCH JOIN을 하게 되면 실제 DB 테이블에서는 어떻게 값을 가져오는 것일까? 

    MEMBER, TEAM TABLE에 다음과 같은 값들이 저장되어있다. 이 때, MEMBER를 기준으로 INNER JOIN을 하기 때문에 회원4와 팀C는 해당이 되지 않는다. INNER JOIN한 결과 오른쪽 테이블이 하나 만들어지게 된다. SQL에서 쿼리가 실행되면 실제로 반환되는 테이블을 가장 오른쪽의 테이블이다. 그렇다면 엔티티 관점에서는 어떻게 동작할까? 

    먼저 LEFT JOIN으로 불렀기 때문에 회원 TABLE에도 있고, TEAM TABLE에도 있는 회원 엔티티들이 불러진다. 이 회원 엔티티들은 .getResultList() 메서드를 통해서 List 형태로 반환되기 때문에 Collection 계열에 저장되어있다. 그리고 각 회원 엔티티는 내부에 Team 엔티티에 대한 참조를 가지고 있기 때문에 실제 엔티티는 위와 같은 구성되게 된다.

     

    Fetch Join 실제 실행해보기


                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.setName("member1");
                member1.setTeam(teamA);
                em.persist(member1);
    
                Member member2 = new Member();
                member2.setName("member2");
                member2.setTeam(teamA);
                em.persist(member2);
    
    
                Member member3 = new Member();
                member3.setName("member3");
                member3.setTeam(teamB);
                em.persist(member3);
    
    
                Member member4 = new Member();
                member4.setName("member4");
                em.persist(member4);
    
                em.flush();
                em.clear();
    
    
                List<Member> members = em.createQuery("select m from Member m join fetch m.team", Member.class).getResultList();// 전체 멤버를 조회하는 select jpql 쿼리
    
                String query = "SELECT m FROM MEMBER m JOIN FETCH m.team";
    
                System.out.println("====================");
                for (Member member : members) {
                    System.out.println("team_name = " + member.getTeam().getName());
                }
                System.out.println("====================");

    위 코드는 위 그림과 최대한 유사한 상황을 만들었다. Member와 Team 엔티티를 만들어서 각각 DB에 저장 후, Flush 및 Clear를 통해 1차 캐시를 깔끔하게 만들어두고 위 코드를 실행해봤다. 

    실행을 하게 되면, MEMBER와 TEAM에 대한 한 방 쿼리가 나가게 되는 것을 볼 수 있다. 그리고 실제로 MEMBER들을 출력해보면 아무런 추가적인 SELECT 쿼리 없이 MEMBER들이 잘 출력되는 것을 볼 수 있다. 

     

    FETCH JOIN이 왜 쓸만한거야? 


    FETCH JOIN을 쓰게 되면 흔히 발생할 수 있는 N+1 문제에서 조금쯤은 자유로워 질 수 있기 때문이다. 예를 들어 위와 똑같은데 FETCH JOIN이 아닌 그냥 MEMBER를 불러오는 SELECT 쿼리를 날려보자. 그러면 결과를 바로 알 수 있을 것이다.

    <작성중>

    댓글

    Designed by JB FACTORY