SpringFramework 5.3/SpringBoot 2.4にて、コンストラクタ呼び出しでマッピングを行うDataClassRowMapperが追加されました。
これを用いることで、BeanPropertyRowMapperと同様の書き味でKotlinのdata classへのマッピングも行うことができます。
実装としてもBeanPropertyRowMapperを継承したものであるため、移行はシームレスに行えると思います。
実際にマッピングしてみる
簡単な検証コードで動作を紹介します。
以下が検証用コードの全体です(長いため折りたたみます)。
内容としては、Kotlinのdata classであるFooに関してマッピングを行っています。
テストコード全体
import org.h2.jdbcx.JdbcDataSource
import org.junit.jupiter.api.AfterAll
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.BeforeAll
import org.junit.jupiter.api.DisplayName
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.TestInstance
import org.junit.jupiter.api.assertThrows
import org.springframework.beans.BeanInstantiationException
import org.springframework.jdbc.core.BeanPropertyRowMapper
import org.springframework.jdbc.core.DataClassRowMapper
import org.springframework.jdbc.core.JdbcTemplate
import org.springframework.jdbc.core.namedparam.BeanPropertySqlParameterSource
import org.springframework.jdbc.core.simple.SimpleJdbcInsert
import javax.sql.DataSource
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
@DisplayName("DBを用いてマッピングを行うテスト")
class UseDBMappingTest {
enum class FooStatus {
active, archive, deleted
}
data class Foo(
val fooId: Int,
val fooName: String,
val fooStatus: FooStatus,
val description: String?
)
data class FooInsert(
val fooId: Int,
val fooName: String,
private val fooStatus: FooStatus,
val description: String?
) {
fun getFooStatus(): String = fooStatus.name
}
lateinit var jdbcTemplate: JdbcTemplate
@BeforeAll
fun beforeAll() {
val dataSource: DataSource = JdbcDataSource().apply {
setUrl("jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1;INIT=CREATE SCHEMA IF NOT EXISTS APP\\;SET SCHEMA APP;")
}
jdbcTemplate = JdbcTemplate(dataSource)
jdbcTemplate.execute(
"""
CREATE TABLE IF NOT EXISTS `foo_table` (
`foo_id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
`foo_name` VARCHAR(255) NOT NULL,
`foo_status` ENUM('active', 'archive', 'deleted') NOT NULL,
`description` VARCHAR(1023) NULL DEFAULT NULL,
PRIMARY KEY (`foo_id`)
);
""".trimIndent()
)
val data = FooInsert(10, "Foo", FooStatus.archive, null)
SimpleJdbcInsert(jdbcTemplate).withTableName("foo_table").execute(BeanPropertySqlParameterSource(data))
}
@Test
fun testBeanPropertyRowMapper() {
assertThrows<BeanInstantiationException> {
jdbcTemplate.queryForObject("SELECT * FROM foo_table", BeanPropertyRowMapper(Foo::class.java))
}
}
@Test
fun testDataClassRowMapper() {
val result = jdbcTemplate.queryForObject("SELECT * FROM foo_table", DataClassRowMapper(Foo::class.java))
assertEquals(Foo(10, "Foo", FooStatus.archive, null), result)
}
@AfterAll
fun afterAll() {
jdbcTemplate.dataSource!!.connection.close()
}
}
重要なのは以下の部分です。
Fooはコンストラクタしか定義していないため、BeanPropertyRowMapperではマッピングできず1、BeanInstantiationExceptionになります。
一方、DataClassRowMapperを利用した場合、正常にマッピングを行うことができています。
@Test
fun testBeanPropertyRowMapper() {
assertThrows<BeanInstantiationException> {
jdbcTemplate.queryForObject("SELECT * FROM foo_table", BeanPropertyRowMapper(Foo::class.java))
}
}
@Test
fun testDataClassRowMapper() {
val result = jdbcTemplate.queryForObject("SELECT * FROM foo_table", DataClassRowMapper(Foo::class.java))
assertEquals(Foo(10, "Foo", FooStatus.archive, null), result)
}