ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Spring 테이블 칼럼이 아닌 필드 데이터 받아오기
    개발/spring 2021. 3. 5. 20:00

    Spring으로 쿼리를 만들다보면 여러개의 테이블을 조인한 쿼리에서 다른 테이블 칼럼의 값까지 읽어올 필요가 있다. 예를 들면 글정보를 받아 오는 api가 있는데 내가 그 글을 좋아요 했는지, 안했는지 유무까지 알려주는 요구 사항의 경우 두 개의 테이블을 조인해야한다.

     

    쿼리문을 짜면 다음과 같다. tb_post에 있는 모든 필드를 가져오고, 좋아요 유무는 liked 필드 이름으로 받아오는 것으로 뒀다.

    select tb_post.*, when tb_post_user_like.post_id > 0 then true else false end as liked from tb_post
    left join tb_post_like on (tb_post.post_id = tb_post_user_like.post_id and tb_post_like.user_id = :findUserId)
    where tb_post.post_id = :postId

     

    JPA에서 제공하는 CrudRepository 로는 이미 있는 테이블의 칼럼을 매핑해서 받아오는데 반해 이 방법은 임의로 liked라는 새 칼럼을 생성한 것이기 때문에 테이블과 1:1 매핑된 엔티티 클래스에서 했던 것처럼 칼럼을 자동으로 매핑하는게 안되고 다른 방법을 써야한다. 열심히 구글 검색을 해본 결과 세가지 방법을 발견했다. 

     

    1. Object형태로 받아오기 

    Query문에서 리턴 값을 Object로 받아오면 모든 필드 값의 리턴을 받아 올 수 있다. 가장 직관적이고 쉬운 방법이다.

     

    @Query(value = "select tb_post.*, case when tb_post_like.post_id > 0 then true else false end as liked from tb_post \n" +
            "from tb_post \n" +
            "left join tb_post_like on (tb_post.post_id = tb_post_like.post_id and tb_post_like.user_id = :findUserId)\n" +
            "where tb_post.post_id = :postId", nativeQuery = true)
    fun findPostById(@Param("postId") postId: Long, @Param("findUserId") findUserId: Long) : List<Array<Any>>

    하지만 이렇게 읽어오면 아래 그림처럼 필드 값이 생략돼서 날라오게 돼서 알아보기가 힘들다. 쿼리문에서 칼럼 필드 순서를 지정하는 방법으로 처리할 수 있으나 알아보기가 힘들어서 관리하기가 어려운 단점이 있어 추천하지 않는다. 위와 같은 형태로 읽어오는 클래스를 만든다면 더더욱 쓰지 않는게 좋다.

     

     

    2. JPA New 명령어 

     

    JPA 쿼리의 New 명령어를 사용하면 리턴 값을 클래스로 줄 수 있다. 단 이 방법은 native query 문을 사용하지 못하고 jpa 쿼리를 사용해야 한다는 점이다. limit을 쓰는 구문에서는 사용할 수 없다.

     

    data class Post(postId: Long, postTitle: String, postContent: String, liked:Boolean)
    
    @Query(value = "select new com.package.Post(tb_post.post_id, tb_post.post_title, tb_post.post_content, case when tb_post_like.post_id > 0 then true else false end) from tb_post from tb_post left join tb_post_like on (tb_post.post_id = tb_post_like.post_id and tb_post_like.user_id = :findUserId) where tb_post.post_id = :postId")
    fun findPostById(@Param("postId") postId: Long, @Param("findUserId") findUserId: Long) : List<Post>

     

    3. SetQueryResultSetMapping 

     

    쿼리에서 읽어온 컬럼 필드를 클래스에 매핑 해줄 수 있는 어노테이션이다. ConstructorResult 어노테이션에서 칼럼 필드 값을 읽어와서 Post 클래스의 생성자로 만들 수 있다. 하단의 NamedNativeQuery 어노테이션에서는 쿼리의 이름을 정하고, ConstructorResult에서 참조하는 필드의 형태로 읽어 올 수 있도록 Select 문을 만들어 주고 사용할 mapping을 SqlResultSetMapping에서 지정한 이름과 동일한 값을 입력한다. 이렇게 하면 이 쿼리는 자동으로 Post 클래스 값을 출력하는 쿼리가 된다.

     

    @SqlResultSetMapping(
            name = "PostMapping",
            classes = [
                    ConstructorResult(
                            targetClass = Post::class,
                            columns = [
                                    ColumnResult(name = "post_id", type = Long::class),
                                    ColumnResult(name = "post_title", type = String::class),
                                    ColumnResult(name = "post_content", type = String::class),
                                    ColumnResult(name = "liked", type = Boolean::class)
                            ]
                    )
            ]
    )
    @NamedNativeQueries(value = [
        NamedNativeQuery(name = "findPostByIdBaseOnUser", query = "select tb_post.post_id, tb_post.post_title, tb_post.post_content, case when tb_post_like.post_id > 0 then true else false end as liked from tb_post \n" +
                "left join tb_post_like on (tb_post.post_id = tb_post_like.post_id and tb_post_like.user_id = :findUserId)\n" +
                "where tb_post.post_id = :postId resultSetMapping = "PostMapping")
    ])
    @Entity
    data class Post(
            @Id
            @GeneratedValue(strategy = GenerationType.IDENTITY)
            val postId: Long,
            @Column
            val postTitle: String,
            @Column
            val postContent: String,
            @Column
            val liked: Boolean = false
    ): Serializable

     

    아래의 코드로 쿼리 호출이 가능하다. 앞서 설저한 NamedNativeQuery의 이름 값과 동일하게 넣는다. 초반에 보일러플레이트 코드가 많지만 Post 클래스를 지속적으로 사용하고자 한다면 필드 값을 참조 할 수 있기 때문에 관리가 더 편리하다. 

     

    @Service
    class PostService {
        @PersistenceContext
        lateinit var entityManager: EntityManager
    
        fun postById(postId: Long, findUserId: Long): Post? {
            return entityManager.createNamedQuery("findPostByIdBaseOnUser", Post::class.java)
                    .setParameter("postId", postId)
                    .setParameter("findUserId", findUserId)
                    .resultList
                    .firstOrNull()
    
        }
    }

    '개발 > spring' 카테고리의 다른 글

    spring batch  (1) 2023.06.30
    IoC container and Bean  (0) 2021.09.06
    @Bean vs @Component  (0) 2021.09.06
    Node.js vs Spring Boot  (5) 2021.03.13

    댓글

Designed by Tistory.