1
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Spring BootでMybatisを使う with Kotlin

Last updated at Posted at 2020-09-03

はじめに

Spring Bootで、Mybatisを使う際Javaと同じ構成だと動かずハマった...
しかし、「Spring x Kotlin x Mybatis」の構成は、調べてもあまり情報が出てこない。

なんとか、解決できた際のコードを紹介する。

目次

  • 実行環境
  • 設定していく
    • 依存ライブラリの追加
    • RDBへの接続プロパティを記述する
    • プロパティ保持クラスを用意する
    • セッションファクトリ、セッションテンプレートのBeanを定義する
    • SQLを発行し、結果を得る
  • おわりに
    • KotlinのORMについて
    • Mybatis Generatorについて
  • 参考

実行環境

Mybatisは、Spring拡張のものを使う。

ライブラリ バージョン
Kotlin/JVM 1.3.72
Spring Boot 2.3.3.RELEASE
Mybatis(mybatis-spring-boot-starter) 2.1.3
Mybatis Generator 1.3.7
RDB Oracle Database 18c

設定していく

準備するものは、ざっくりわけると

  • build.gradle.kts
  • application.properties
  • application.propertiesを動的に読み込むKotlinクラス
  • DB接続ソース設定クラス
  • Mybatis関連のMapper

application.properties の値を動的に読み込み、DataSourceに設定できるようにする。

依存ライブラリの追加

なにはなくとも、まずは依存ライブラリから。

build.gradle.kts
dependencies {
	/**
	 * Spring
	 */
	implementation("org.springframework.boot:spring-boot-starter-web")
	implementation("org.springframework.boot:spring-boot-starter-aop")
	runtimeOnly("org.springframework.boot:spring-boot-starter-tomcat")

	implementation("com.fasterxml.jackson.module:jackson-module-kotlin")
	implementation("org.jetbrains.kotlin:kotlin-reflect")
	implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")

	/**
	 * Mybatis、Driver
	 */
	implementation("org.mybatis.spring.boot:mybatis-spring-boot-starter:2.1.3")
	implementation(files("libs/ojdbc8.jar")) // ローカルのDriver jarを追加する
	mybatisGenerator("org.mybatis.generator:mybatis-generator-core:1.3.7") // ここでは細かくは触れないが、テーブル定義からマッパーを自動生成する

	testImplementation("org.springframework.boot:spring-boot-starter-test") {
		exclude(group = "org.junit.vintage", module = "junit-vintage-engine")
	}
}

RDBへの接続プロパティを記述する

application.propertiesに、読み込むプロファイルごとのユーザ名やパスワードを記述する。
ここは実行環境に合わせて書き換える。

application.properties
spring.datasource.url=jdbc:oracle:thin:@localhost:1521/DB
spring.datasource.username=<username>
spring.datasource.password=<password>
spring.datasource.driverClassName=oracle.jdbc.driver.OracleDriver

今回は1枚だけだが、テスト・本番と分けたい場合は同じようにして application-ut.properties , application-prod.properties を用意すればよいです。

プロパティ保持クラスを用意する

アプリケーションを起動した際に、プロファイルを判断して application.properties の値を Javaオブジェクトに保持させるクラスを用意する。
このクラスは、Beanをインスタンス化する際に値を詰めるクラスなので、プロパティはすべて lateinit な変数としておく。次に用意するセッションファクトリクラス内 DataSource インスタンスに設定する。

他のBeanに混ぜて使えるようにするべく、 @Component アノテーションを付与し、 DataSource を定義するクラスに注入する。
クラスのフィールド名は application.properties の項目に揃える。

PropertyHolder.kt
@Component
@Configuration
@ConfigurationProperties(prefix = "spring.datasource")
class PropertyHolder {
    lateinit var username: String
    lateinit var password: String
    lateinit var url: String
    lateinit var driverClassName: String
}

セッションファクトリ、セッションテンプレートのBeanを定義する

Mybatisのセッションファクトリ、セッションテンプレートを定義する。
このBean定義により、アプリケーションが起動するとコネクションを張る準備ができる。

SessionFactory.kt
@Configuration
@MapperScan(
        basePackages = ["com.example.api.dao","com.example.api.repository"],
        sqlSessionTemplateRef = "SqlSessionTemplate"
)
class SqlSessionFactory(private val propertyHolder: PropertyHolder) { // ↑で定義したプロパティを持っているクラスを注入する。

    companion object {
        // XMLのありか
        // クラスの外にstaticフィールドとして切り出してもいい
        const val MAPPER_XML_PATH: String = "classpath:com/example/api/**/*.xml" 
    }

    /**
     * データソース
     */
    @Bean(name = ["DataSource"])
    @ConfigurationProperties(prefix = "spring.datasource")
    fun dataSource(): DataSource? {
        // プロパティから読み込んでデータソースにセットする
        return PooledDataSource().apply {
            url = propertyHolder.url
            username = propertyHolder.username
            password = propertyHolder.password
            driver = propertyHolder.driverClassName
        }
    }

    /**
     * SQLセッションファクトリ
     */
    @Bean(name = ["SqlSessionFactory"])
    @Throws(Exception::class)
    fun sqlSessionFactory(@Qualifier("DataSource") dataSource: DataSource?): SqlSessionFactory? {
        val bean: SqlSessionFactoryBean = SqlSessionFactoryBean()
        bean.setDataSource(dataSource)
        bean.setMapperLocations(*PathMatchingResourcePatternResolver().getResources(MAPPER_XML_PATH))
        return bean.getObject()
    }

    /**
     * SQLセッションテンプレート
     */
    @Bean(name = ["SqlSessionTemplate"])
    @Throws(java.lang.Exception::class)
    fun sqlSessionTemplate(@Qualifier("SqlSessionFactory") sqlSessionFactory: SqlSessionFactory?): SqlSessionTemplate? {
        return SqlSessionTemplate(sqlSessionFactory)
    }

}

特に、Kotlinで記述する際のポイントは↓ここ。

fun dataSource(): DataSource? {
    // プロパティから読み込んでデータソースにセットする
    return PooledDataSource().apply {
        url = propertyHolder.url
        username = propertyHolder.username
        password = propertyHolder.password
        driver = propertyHolder.driverClassName
    }
}

Javaと違い、 DataSouorce のコンストラクタに明示的に値を渡してインスタンスをつくらないといけないらしい。なんか違うような気がするが...とにもかくにもこう書くことで乗り切れた。
昔、ほぼ同じコードをJavaでやったことがあり、そのまま移植したらハマった。全然違った...
Javaでは、単に空のコンストラクタでインスタンスを初期化すれば @ConfigurationProperties(prefix = "spring.datasource") から勝手に DataSource の準備をしてくれていた。
はじめ、これが全然わからなくてMybatisの例外にドハマリしました...この設定方法紹介してる記事も全然なくて、まじでわからなかった...

SQLを発行し、結果を得る

さて、ここまでで準備が完了。あとは、手書きSQLでも自動生成コードでも、好きなMybatisのマッパーをロジッククラスにDIしてSQLを発行すればいい。

MybatisGeneratorの自動生成コードでいくなら、こんな感じ。
ここでは、 Customer というテーブルがRDBにあり、ジェネレータでエンティティ、例オブジェクト、マッパーがあると仮定する。
※本記事の本筋ではないので、インターフェースやら例外処理やらは省き最低限のSQL発行関連のみ記述。

CustomerServiceImpl.kt
class CustomerServiceImpl(private val customerRepository: CustomerRepository) {

    override fun retrieve(req: List<String>): List<Customer> {

        val condition: CustomerExample = CustomerExample()
        condition.createCriteria().andEmailIn(req)

        // クエリを実行する
        val customers: List<Customer> = customerRepository.selectByExample(condition)

        return customers
    }
}

おわりに

KotlinのORMについて

やや実装を急いでいたため、一から学習するのがためらわれたが、KotlinのORMライブラリとしては Exposedが便利で扱いやすそうである。
ActiveRecordのような、ORM然としたライブラリである印象を受ける。

SQLの発行やトランザクション管理などは、DSLによりラップされ、ライブラリが制御をしているのかも。
今回はORMにMybatisを選択したものの、自動生成コードはJavaになってしまうし、なんだかんだxmlを使うことになってカスタマイズが効く一方で設定やらdebugがかったるい。
その点、ExposedはKotlinネイティブ、軽量コンパクトな点はとても魅力的なように思える。

一方で、今回は小さいサービスの実装だったので選択肢としてナシではなかったものの、大きめのサービスを作る場合はSQLをある程度柔軟に扱えるMybatisのほうが気が楽なように思える。
詳しくはExposedのREADMEを参照していただければ。

Mybatis Generatorについて

Mybatis Generatorは、1.3系と1.4系で自動生成できるコードがかなり異なる。
generator関連の公式ドキュメントがぱっとは見当たらず(未だに見たことがない)、使い慣れた1.3系の状態でコードを生成した。

generator用のGradleビルドスクリプトも、groovyとkotlin DSL結構違った。最適化は多少苦労しました...
ここで書くとまた長くなってしまうので、機会があれば別の記事で。

参考

1
3
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
1
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?