Spring Data JPA - EntityGraph

Spring Data JPA

백기선님의 강의인 Spring Data JPA 강의를 듣고 공부한 내용을 정리한 글

Fetch 모드

스프링 JPA에서는 쿼리 메소드 마다 연관 관곙의 Fetch 모드를 설정할 수 있다.

아래의 예제를 보자.

@Entity
class Comment(
    @Id @GeneratedValue
    val id: Long? = null,
    var comment: String? = null,
    @ManyToOne()
    var post: Post? = null,
    var likeCount: Int? = null
)

Comment 클래스는 Post 클래스와 1:N 관계이다.

    @Test
    fun getComment() {
        commentRepository.findByIdOrNull(1L)
    }

하이버네이트에서 어떻게 쿼리를 날리는지 알아보기 위해 테스트 코드를 작성하였다.

Hibernate: 
    select
        comment0_.id as id1_1_0_,
        comment0_.comment as comment2_1_0_,
        comment0_.like_count as like_cou3_1_0_,
        comment0_.post_id as post_id4_1_0_,
        post1_.id as id1_2_1_,
        post1_.content as content2_2_1_,
        post1_.created as created3_2_1_,
        post1_.title as title4_2_1_ 
    from
        comment comment0_ 
    left outer join
        post post1_ 
            on comment0_.post_id=post1_.id 
    where
        comment0_.id=?

그리고 콘솔에 찍히는 하이버네이트 쿼리를 보면 left outer 조인을 사용하여 post까지 조회해오는 것을 확인할 수 있다.

기본적으로 ManyToOne의 관계에서 Fetch의 기본 값은 EAGER이다.

@Entity
class Comment(
    @Id @GeneratedValue
    val id: Long? = null,
    var comment: String? = null,
    @ManyToOne(fetch = FetchType.EAGER)
    var post: Post? = null,
    var likeCount: Int? = null
)

애노테이션의 끝이 One으로 끝나는 것들은 모두 EAGER이며 Many로 끝나는 경우는 LAZY이다.

EAGER인 경우 comment를 조회할 때 post 값도 같이 조회해오게 된다.

이 옵션을 LAZY로 바꾸고 테스트해보자.

@Entity
class Comment(
    @Id @GeneratedValue
    val id: Long? = null,
    var comment: String? = null,
    @ManyToOne(fetch = FetchType.LAZY)
    var post: Post? = null,
    var likeCount: Int? = null
)
Hibernate: 
    select
        comment0_.id as id1_1_0_,
        comment0_.comment as comment2_1_0_,
        comment0_.like_count as like_cou3_1_0_,
        comment0_.post_id as post_id4_1_0_ 
    from
        comment comment0_ 
    where
        comment0_.id=?

놀랍게도 Comment만 조회해오게 된다.

그럼 이게 왜 필요한가에 대한 의문이 생길 수 있다.

기본은 LAZY로 쓰지만 필요한 경우에는 EAGER로 가져올 수 있다.

아래의 예제를 보자.

@Entity
@NamedEntityGraph(name = "Comment.post",
        attributeNodes = [NamedAttributeNode("post")])
class Comment(
        @Id @GeneratedValue
        val id: Long? = null,
        var comment: String? = null,
        @ManyToOne(fetch = FetchType.LAZY)
        var post: Post? = null,
        var likeCount: Int? = null
)

엔터티 객체에 NamedEntityGraph 애노테이션을 사용하여 연관관계를 설정해주고 실제 쿼리 메소드는 레포지토리에 명시해주어야 한다.

interface CommentRepository: JpaRepository<Comment,Long> {
    @EntityGraph(value = "Comment.post")
    fun getById(id:Long):Comment?
}

참고로 NamedEntityGraph를 사용하더라도 기본 타입들은 EAGER로 가져오게 된다.

차이를 이해하기 위해 테스트 코드를 작성해보자.

    @Test
    fun getComment() {
        commentRepository.getById(1L)

        println("===================================")

        val comment = commentRepository.findByIdOrNull(1L)
    }

실행 시켜서 하이버네이트 쿼리를 확인한다.

Hibernate: 
    select
        comment0_.id as id1_1_0_,
        post1_.id as id1_2_1_,
        comment0_.comment as comment2_1_0_,
        comment0_.like_count as like_cou3_1_0_,
        comment0_.post_id as post_id4_1_0_,
        post1_.content as content2_2_1_,
        post1_.created as created3_2_1_,
        post1_.title as title4_2_1_ 
    from
        comment comment0_ 
    left outer join
        post post1_ 
            on comment0_.post_id=post1_.id 
    where
        comment0_.id=?

잘 보면 FETCH 전략을 LAZY로 줬음에도 불구하고 post를 가져온 것을 볼 수 있다.

이를 응용하면 쿼리 메소드마다 FETCH 전략을 다르게 가져갈 수 있다는 장점이 있다.

기존 쿼리를 보면,

Hibernate: 
    select
        comment0_.id as id1_1_0_,
        comment0_.comment as comment2_1_0_,
        comment0_.like_count as like_cou3_1_0_,
        comment0_.post_id as post_id4_1_0_ 
    from
        comment comment0_ 
    where
        comment0_.id=?

기존 FETCH 전략을 따르는 것을 확인할 수 있다.

Reference

인프런 백기선님의 스프링 Data JPA



© 2022. by minkuk

Powered by minkuk