Post

(Spring Data JDBC) JdbcOperations으로 확장하기

Spring Data JDBC에서 native query 사용하기

Spring Data JDBC를 사용할 때, CrudRepository와 자동 생성 쿼리 만으로는 커버가 되지 않는 경우가 반드시 생긴다.

  • 복합키 사용하는 경우
  • 조인해서 가져와야 하는 경우 (주로 정규화로 인해. 또는 성능으로 인해.)
  • upsert 해야 하는 경우 (select-insert/update 2쿼리가 아니라 MERGE INTO 같은 1쿼리 쓰고 싶을 때)

결국 직접 작성한 native query를 사용해야 하는 경우가 반드시 생긴다는 것이다.

직접 작성한 native query를 사용하기 위해서는 크게 2가지 접근법이 있다.

  • @Query에 쿼리 명시하는 방법
  • JdbcOperations.getForObject 에 쿼리 명시하는 방법 (일반적인 JDBC)

두 가지 방법 중, jdbcOperations 사용하는 방법이 훨씬 유연해보인다.
@Query의 단점을 생각해보면 아래와 같다.

@Query는 애너테이션에 문자열 넘기는 방식이라, 복잡한 조건이나 변환은 spel을 사용해야 한다.

  • https://stackoverflow.com/questions/62580440/spring-data-jdbc-spel
  • spel을 쓰다 보니, LocalDateTime 같은 타입이 spel 단에서 String으로 변환이 되면서, Converter가 적용되지 않는 문제가 있다. (AbstractJdbcConfiguration에서 converter를 등록해도 CrudRepository를 통한 Insert/Update에만 적용되지, 직접 @Query 작성한 Insert/Update 경우에는 변환되지 않는다)
1
2
3
4
5
ON (
  mrc_no = :#{#groupMerchantInfo.merchantNo} AND
  reg_ymd = :#{#groupMerchantInfo.registrationDate}
) // registrationDate converter 적용될거라 기대하지만 그렇지 않다.

@Query 로 native query가 매핑된 메서드는 Pageable 파라미터를 넣어도 pagination이 되지 않는다.

1
2
3
Caused by: java.lang.UnsupportedOperationException:
    Page queries are not supported using string-based queries. Offending method: ...
    at org.springframework.data.jdbc.repository.query.StringBasedJdbcQuery.<init>(StringBasedJdbcQuery.java:106)

Note that String-based queries do not support pagination nor accept SortPageRequest, and Limit as a query parameter as for these queries the query would be required to be rewritten. If you want to apply limiting, please express this intent using SQL and bind the appropriate parameters to the query yourself.

  • @Query에 들어가는 쿼리는 native query로, 각 DBMS의 SQL dialect에 맞게 자동으로 쿼리를 pagination 쿼리로 변환해주는 기능은 없기 때문에 자동 pagination은 되지 않는 것.
  • 아예 native query 자체를 pagination query로 작성하고 pagination param도 넣어주어야 한다. (그럴 바에야 jdbcOperations 쓰는게 낫다)

위와 같은 단점을 가지고 있는데 굳이 @Query를 쓸 이유가 없다. (안되는게 많은데 굳이)

JdbcOperation으로 Spring Data JDBC Repository 확장하기

CrudRepository를 아래와 같이 Dao로 확장해서 관리하는 것이 좋다.

1
2
interface MerchantInfoRepository 
    : MyJdbcRepository<MerchantInfo, MerchantInfo.CompositeKey>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Repository
class MerchantInfoDao(
    private val merchantInfoRepository: MerchantInfoRepository,
    private val jdbcOperations: NamedParameterJdbcOperations
): MerchantInfoRepository by merchantInfoRepository {  // delegate 처리해서 interface 노출
    fun upsert(merchantInfo: MerchantInfo): Int {
        return jdbcOperations.update(
            upsertQuery,
            ObjectSqlParameterSource(merchantInfo)
        )
    }
    private val upsertQuery = """
        MERGE INTO mrc
        USING DUAL
        ON (mrc_no = :merchantNo...)    // bind는 반드시 $가 아니라 :로. (prepared statement)
        ..."""
}

JdbcOperation 상세

This post is licensed under CC BY 4.0 by the author.