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