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)
}