LoginSignup
0
1

More than 3 years have passed since last update.

【SpringJdbc】DataClassRowMapperを用いコンストラクタ呼び出しでマッピングを行う【Java/Kotlin】

Posted at

SpringFramework 5.3/SpringBoot 2.4にて、コンストラクタ呼び出しでマッピングを行うDataClassRowMapperが追加されました。

これを用いることで、BeanPropertyRowMapperと同様の書き味でKotlindata classへのマッピングも行うことができます。
実装としてもBeanPropertyRowMapperを継承したものであるため、移行はシームレスに行えると思います。

実際にマッピングしてみる

簡単な検証コードで動作を紹介します。

以下が検証用コードの全体です(長いため折りたたみます)。
内容としては、Kotlindata 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ではマッピングできず1BeanInstantiationExceptionになります。
一方、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)
}
0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1