@EntityGraph와 fetch join 차이

taehee kim·2023년 3월 5일
0

0. 차이점

  • Repository 테스트 코드를 작성하던 중 EntityGraph와 fetch join에서 예상치 못한 동작이 발생하여 글을 작성하게 되었습니다.

  • 결론적으로 EntityGraph의 경우 fetchType을 eager로 변환하는 방식으로 outer left join을 수행하여 데이터를 가져오지만, fetch join의 경우 따로 outer join으로 명시하지 않는 경우 inner join을 수행한다는 점입니다.

  • 이 때문에 fetch join의 단점을 피할 수 있습니다.(1:N 컬렉션 join시 하나만 join할 수 있는 제약, distinct사용)

  • 이렇게 되는 이유는 EntityGraph의 경우 fetchType을 전환하여 조회하는 개념이기 때문입니다.이 이전에는 Spring Data JPA를 통해 fetch join 을 쿼리 없이 편리하게 작성하는 방법으로 오인하고 있었습니다.)

1. 발생 상황

  • 엔티티 간의 관계는 Article이 1 ArticleMember(MatchCondition)가 N으로 연관관계를 맺고 있는 상황이며 1인 Article을 대상으로 조회를 하면서 fetch join을 하거나 Entity Graph 기능을 통해 컬렉션 N으로 연관관계를 맺고 있는 대상을 같이 조회하는 경우입니다.
  • QueryDsl로 작성한 fetch join과 변환된 sql문에서 inner join으로 변환되었음을 확인할 수 있습니다.
JPAQuery<Article> query = queryFactory.select(article).distinct()
            .from(article)
            .join(article.articleMembers, articleMember).fetchJoin()
            .where(
                isDeletedIsFalse(),
                isComplete(condition.getIsComplete()),
                isContentCategory(condition.getContentCategory()),
                isAnonymity(condition.getAnonymity())
            );
	select ...
    from
        article article0_ 
    inner join
        article_member articlemem1_ 
            on article0_.article_id=articlemem1_.article_id 
    where
        article0_.is_deleted=? 
    order by
        article0_.created_at asc
  • 반면 EntityGraph를 적용한 Spring Data JPA메서드는
    left outer join을 수행한다.
    @EntityGraph(attributePaths = {"articleMatchConditions"})
    Optional<Article> findDistinctFetchArticleMatchConditionsByApiIdAndIsDeletedIsFalse(String articleId);
    from
        article article0_ 
    left outer join
        article_match_condition articlemat1_ 
            on article0_.article_id=articlemat1_.article_id 
    where
        article0_.api_id=? 
        and article0_.is_deleted=0
  • Article을 조회하는 메서드를 테스트 할 시 Article만 생성하여 조회를 수행하는 방식으로 테스트 하고 싶었는데 EntityGraph방식에서는 Article에서 같이 조회 해오려는 collection Entity가 비어있더라도 Article은 정상적으로 조회가 되기 때문에 문제가 없었지만 fetch join에서는 Article이 있더라도 항상 조회 개수가 0이 되었습니다(물론 fetch join의 경우에도 outer join으로 수행할 수도 있습니다.).

1. @EntityGraph동작 방식

  • 기본적으로 fetchType.Lazy, fetchType.Eager는 static정보로 runtime에 변경할 수 없습니다.
  • EntityGraph는 이를 변경할 수 있게 하는 기능으로 fetchType.Lazy로 설정해놓은 경우에 fetchType.Eager로 사용할 수 있습니다.
  • 이 때문에 fetch join에서 1:N연관관계로 조회 할경우 1개의 컬렉션까지밖에 최대 같이 조회 가능한 부분이나, distinct가 필요한 단점을 극복할 수 있습니다.
  • 다만 1:N 연관관계 컬렉션을 같이 조회 해 오는 경우 paging은 fetch join과 동일하게 수행할 수 없습니다.(수행하면 전체 테이블 join결과를 application으로 가져온 후 memory에서 paging처리를 하기 때문에 메모리 관련 장애가 발생할 수 있습니다.)

https://docs.spring.io/spring-data/jpa/docs/current/api/org/springframework/data/jpa/repository/EntityGraph.html
https://docs.spring.io/spring-data/jpa/docs/current/api/org/springframework/data/jpa/repository/EntityGraph.EntityGraphType.html

2. 결론

  • 단순히 entityGraph가 fetch join을 수행하는 것인지 알았는데 개념적으로 차이가 있음을 알게 되었습니다.
  • 또한 테스트 코드를 작성하면서 fetch join을 적용한 메서드의 경우 join 대상이 어떤것인지에 따라 같은 조회 조건에서도 여러 메서드가 만들어지고 테스트 해야하며 테스트할 때에 연관관계 객체를 생성해야 검증이 가능하기 때문에 코드 유지보수성에서 단점이 다소 있음을 느꼈습니다.
  • 성능 적으로 fetch join을 하여 네트워크 통신 횟수를 줄이는 것이 특별하게 필요하다고 생각되지 않는 경우에는 batch size를 활용하거나 EntityGraph기능을 활용하는 방안을 먼저 고려하는 것도 적극적으로 고려해봐야 합니다.
profile
Fail Fast

0개의 댓글