fetch join vs @EntityGraph

대영·2024년 2월 1일
2

Spring

목록 보기
9/16

🙏내용에 대한 피드백은 언제나 환영입니다!!🙏

앞글에서 N+1문제를 해결하는 방법들에 대해서 알아보았다.
N+1문제에 대해서 공부를 하던 중 @EntityGraph에 대해서 알게 되었다.

📌결과 먼저 보기

Entity와의 Mapping구성은 이전 게시글에서와 똑같이 진행할 것이다.

테스트 구조는 아래와 같다.

<<데이터 넣기>>

	@Test
    public void inputData() {
        for(int i = 0; i < 10; i++) {
            // 10개를 DB에 저장. 1개의 팀당 1명의 멤버 저장.
            Team team = new Team("팀" + i);
            teamRepository.save(team);

            Member member = new Member("멤버" + i);
            member.setTeam(team);
            memberRepository.save(member);
        }
    }

<<조회하기>>

	@Test
    public void find() {
        List<Team> teams = teamRepository.findAll();
        for (Team team : teams) {
            System.out.println("<< " + team.getId() + "의" + team.getMembers() + "에 접근 >>");
        }
        }
    }

👉 fetch join

fatch join을 위해 findAll 메서드를 아래와 같이 설정했다.

public interface TeamRepository extends JpaRepository<Team, Long> {
    @Query("select t from Team t join fetch t.members")
    List<Team> findAll();
}

아래는 조회 결과이다.

Hibernate: 
    select
        t1_0.id,
        m1_0.team_id,
        m1_0.id,
        m1_0.name,
        t1_0.name 
    from
        team t1_0 
    join
        member m1_0 
            on t1_0.id=m1_0.team_id
<<1[com.example.demo.Entity.Member@525aadf2]에 접근 >>
<<2[com.example.demo.Entity.Member@4a4979bf]에 접근 >>
<<3[com.example.demo.Entity.Member@5702e6f3]에 접근 >>
<<4[com.example.demo.Entity.Member@2c10f9f3]에 접근 >>
<<5[com.example.demo.Entity.Member@5166fe7a]에 접근 >>
<<6[com.example.demo.Entity.Member@29f72685]에 접근 >>
<<7[com.example.demo.Entity.Member@56114349]에 접근 >>
<<8[com.example.demo.Entity.Member@4f5d587c]에 접근 >>
<<9[com.example.demo.Entity.Member@39d62e47]에 접근 >>
<<10[com.example.demo.Entity.Member@4d2bcca8]에 접근 >>

👉 @EntityGraph

@EntityGraph를 위해 findAll 메서드를 아래와 같이 설정했다.

public interface TeamRepository extends JpaRepository<Team, Long> {
    @EntityGraph(attributePaths = "members")
    List<Team> findAll();
}

아래는 조회 결과이다.

Hibernate: 
    select
        t1_0.id,
        m1_0.team_id,
        m1_0.id,
        m1_0.name,
        t1_0.name 
    from
        team t1_0 
    left join
        member m1_0 
            on t1_0.id=m1_0.team_id
<<1[com.example.demo.Entity.Member@57e83608]에 접근 >>
<<2[com.example.demo.Entity.Member@526a1b51]에 접근 >>
<<3[com.example.demo.Entity.Member@54632794]에 접근 >>
<<4[com.example.demo.Entity.Member@760f4310]에 접근 >>
<<5[com.example.demo.Entity.Member@77eb76f]에 접근 >>
<<6[com.example.demo.Entity.Member@1efa258e]에 접근 >>
<<7[com.example.demo.Entity.Member@1ae69abe]에 접근 >>
<<8[com.example.demo.Entity.Member@d319d2c]에 접근 >>
<<9[com.example.demo.Entity.Member@5add0cc0]에 접근 >>
<<10[com.example.demo.Entity.Member@6971f5f4]에 접근 >>

무엇이 다른가❓

fetch join과 @EntityGraph의 실행 결과 중 다른 점은 결과를 가져오는 과정이다.

fetch join에서는 "join"을. @EntityGraph에서는 "left join"을 하였다.
이것은 각각 "Inner Join". "Left Outer Join"이다.
그렇다면, 이것이 무엇인가?

👉 Inner Join

Inner Join은 "교집합"이라고 할 수 있다.
이것이 무슨 말이냐면, DB에서 데이터를 조회할 때 Team과 그 해당하는 Member가 들어있어야 조회가 된다는 것이다.

예를 들어 Team1 + Member1은 조회가 되지만 Team2 + null은 조회되지 않는다.

위 코드에서 Team9와 Team10의 Member를 삭제하고 실행해 보겠다.

Hibernate: 
    select
        t1_0.id,
        m1_0.team_id,
        m1_0.id,
        m1_0.name,
        t1_0.name 
    from
        team t1_0 
    join
        member m1_0 
            on t1_0.id=m1_0.team_id
<<1[com.example.demo.Entity.Member@33da0fad]에 접근 >>
<<2[com.example.demo.Entity.Member@106e6dfe]에 접근 >>
<<3[com.example.demo.Entity.Member@31b827a6]에 접근 >>
<<4[com.example.demo.Entity.Member@52bcd7c9]에 접근 >>
<<5[com.example.demo.Entity.Member@59fa5a39]에 접근 >>
<<6[com.example.demo.Entity.Member@7a8dbbf1]에 접근 >>
<<7[com.example.demo.Entity.Member@5d8c03cc]에 접근 >>
<<8[com.example.demo.Entity.Member@6a2d0a19]에 접근 >>

결과를보면 Team은 1부터 10까지 있지만, 팀8까지밖에 출력이 안된다.

👉 Left Outer Join

Left Outer Join은 Inner Join과 반대로 "합집합"이다.
즉, DB에서 데이터를 조회할 때 Team에 Member이 들어있지 않더라도 조회가 된다.

Inner Join과 똑같은 DB로 실행해보겠다.

Hibernate: 
    select
        t1_0.id,
        m1_0.team_id,
        m1_0.id,
        m1_0.name,
        t1_0.name 
    from
        team t1_0 
    left join
        member m1_0 
            on t1_0.id=m1_0.team_id
<<1[com.example.demo.Entity.Member@50c99398]에 접근 >>
<<2[com.example.demo.Entity.Member@4a66949a]에 접근 >>
<<3[com.example.demo.Entity.Member@2f726cf4]에 접근 >>
<<4[com.example.demo.Entity.Member@560e1459]에 접근 >>
<<5[com.example.demo.Entity.Member@d2ca3a9]에 접근 >>
<<6[com.example.demo.Entity.Member@3a33d655]에 접근 >>
<<7[com.example.demo.Entity.Member@70c6c6a7]에 접근 >>
<<8[com.example.demo.Entity.Member@303a484e]에 접근 >>
<<9[]에 접근 >>
<<10[]에 접근 >>

Inner Join과는 다르게 Team 1~10 모두 조회되었다.
하지만, Team9와 Team10에는 Member이 null이기에 들어가있지 않은걸 볼 수 있다.

무엇이 더 좋나❓

만약에 매핑하는 두 테이블 사이에 기준 열이 서로 값을 가지고 있다면 누락되지 않을 것이다.

성능을 비교하자면, Inner Join이 데이터가 없는 것은 가지고 오지 않는 교집합 역할이기에 상대적으로 성능이 좋다.

하지만, 상황에 따라 적절히 사용해야 한다.

그렇다면 @EntityGraph는 왜 사용하는가❓

기본적으로 fetchType.Lazy, fetchType.Eager는 static정보로 runtime에 변경할 수 없다.

@EntityGraph는 이것을 가능하게 한다.
fetchType.Lazy를 fetchType.Eager로 변경할 수 있는 것이다.

이러한 이유로 fetch join의 특징인 중복된 데이터를 들고오는 상황. 예를 들어, Team1 + Member1과 Team1 + Member2는 다르기에 Team1을 두 번 들고오게 되는 경우. (즉, Team1은 Member의 갯수만큼 불러온다.)
그래서, Distinct를 사용하지 않아도 된다.

또한, fetch join에서 일대다 관계인 경우 1개의 컬렉션까지밖에 같이 조회를 못하였는데, 이것 또한 해결할 수 있게 되었다.

하지만, fetch join과 같이 페이징 오류는 계속 발생한다.

이러한 이유로 상황에 따라서는 Left Outer Join을 사용하는 @EntityGraph를 사용하는게 적절할 수 있다.

<참고>

@EntityGraph와 fetch join 차이
[SQL] LEFT JOIN, INNER JOIN 차이

profile
Better than yesterday.

0개의 댓글