Spring Data JPA - Projection
Spring Data JPA
백기선님의 강의인 Spring Data JPA 강의를 듣고 공부한 내용을 정리한 글
Projection
엔티티의 일부 데이터만 가져오기
Select 쿼리에서 일부 필드만 가져올 수 있듯이 JPA에서도 이러한 기능을 지원하고 있다.
이 기능의 이름을 Projection이라 부른다.
JPA에서는 늘 모든 필드를 가져왔었는데 일부만 가져와보자.
크게 인터페이스 기반과 클래스 기반으로 사용할 수 있다.
먼저 Closed 프로젝션에 대해 알아보자.
Closed 프로젝션
@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,
val up: Int,
val down: Int,
val best: Boolean
)
기존 Comment에 up, down, best라는 필드를 추가해보았다.
interface CommentSummary {
fun getComment(): String
fun getUp(): Int
fun getDown(): Int
}
CommentSummary라는 인터페이스를 선언하고 해당 필드를 가져올 수 있는 메소드 이름을 매핑했다. (get + 필드명)
interface CommentRepository: JpaRepository<Comment,Long> {
fun findByPostId(id:Long): List<CommentSummary>
}
레포지토리에서 방금 만든 CommentSummary 인터페이스로 조회해올 경우 하이버네이트 쿼리는 다음과 같이 발생하게 된다.
select
comment0_.comment as col_0_0_,
comment0_.up as col_1_0_,
comment0_.down as col_2_0_
from
comment comment0_
left outer join
post post1_
on comment0_.post_id=post1_.id
where
post1_.id=?
이러한 쿼리가 바로 Closed 프로젝션 방식이다.
가져오려는 컬럼만 가져올 수 있으므로 최적화가 가능하다.
Java 8의 디폴트 메소드를 사용하면 연산 또한 가능하다.
Open 프로젝션
@Value(SPEL)
애노테이션을 사용해서 연산을 하는 방법이다.
쿼리 최적화가 불가능하고 SPEL을 사용해야한다.
interface CommentSummary {
fun getComment(): String
fun getUp(): Int
fun getDown(): Int
@Value("#{target.up + ' ' + target.down}")
fun getVotes():String
}
단점은 성능 최적화가 안된다.
왜? SPEL에서 사용한 target은 Comment 객체를 의미한다.
즉 Comment 전체를 들고와서 해당하는 필드만 뽑아서 문자열을 만들어주는 쿼리이기 때문이다.
때때로 이런 경우가 필요하다면 사용해볼 수는 있겠다.
select
comment0_.id as id1_1_,
comment0_.best as best2_1_,
comment0_.comment as comment3_1_,
comment0_.down as down4_1_,
comment0_.like_count as like_cou5_1_,
comment0_.post_id as post_id7_1_,
comment0_.up as up6_1_
from
comment comment0_
left outer join
post post1_
on comment0_.post_id=post1_.id
where
post1_.id=?
하이버네이트 쿼리를 보면 알겠지만 Open 프로젝션을 사용하면 전체 컬럼을 조회해오게 된다.
꽤 비효율적인 것 같은데 Closed 프로젝션으로 원하는 컬럼 값만 가져오는 방법은 없을까?
당연히 있다.
interface CommentSummary {
fun getComment(): String
fun getUp(): Int
fun getDown(): Int
}
fun CommentSummary.getVotes(): String {
return getUp().toString() + " " + getDown().toString()
}
확장함술르 사용해 원하는 컬럼만 가져온 다음
@Test
fun getComments() {
val post = Post()
post.title = "jpa"
val savedPost = postRepository.save(post)
val comment = Comment()
comment.post = savedPost
comment.up = 10
comment.down = 1
commentRepository.save(comment)
commentRepository.findByPostId(savedPost.id!!).forEach {
println("==================")
println(it.getVotes())
println("==================")
}
}
테스트 코드를 작성하여 테스트해보자.
// output
==================
10 1
==================
원하는 컬럼만 Closed 프로젝션으로 가져와보았다.
클래스 기반 프로젝션
DTO처럼 필드명을 주고 만들 수 있다…만 인터페이스가 더 편하다.
Reference
인프런 백기선님의 스프링 Data JPA