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



© 2022. by minkuk

Powered by minkuk