Kotlinでmybatis-spring-boot-starterを使う

以前「[GroovyでMyBatisのMapperをつくる（とSQLの可読性がGood!!）](http://qiita.com/kazuki43zoo/items/0c6ac5acaee1201a433e)」という投稿をしましたが、今回は最近話題？のKotlin上でMaBatis(mybatis-spring-boot-starter)を使ってみました。
なお、題材は以前投稿した「[mybatis-spring-boot-starterの使い方](http://qiita.com/kazuki43zoo/items/ea79e206d7c2e990e478)」と一緒です。

> **Note:**
> 2017/4/10:
> mybatis-spring-boot-starter 1.3.0 (Spring Boot 1.5.2.RELEASE)ベースに更新しました。
>
> 2019/5/3:
> mybatis-spring-boot-starter 2.0.1 (Spring Boot 2.1.4.RELEASE)ベースに更新しました。

# 動作検証バージョン

* Kotlin 1.2.71
* MyBatis Spring Boot 2.0.1
* MyBatis 3.5.1
* MyBatis Spring 2.0.1
* Spring Boot 2.1.4.RELEASE
* Spring Framework 5.1.6.RELEASE

> **Note:IDEは・・・**
>
> もちろんIntelliJ IDEA :smile: （本投稿はIDEなし前提ですが・・・）

# Kotlin版でのアプローチ
Groovy版は主言語はあくまでJavaで、Javaアノテーションの表現の貧弱なところをGroovyの複数行文字列（いわゆる「ヒアドキュメント」に分類される仕組み）を利用することで解消するというアプローチでした。Kotlin版は、主言語自体をKotlinとして、Kotlinの中からMyBatis(mybatis-spring-boot-starter)を利用するというアプローチになっています。

# Kotlinの複数行文字列（ヒアドキュメント！？）を使う

Groovy版と同様に、SQLはKotlinのアノテーションを使って指定します。Kotlinのアノテーションを利用すると以下のような感じでSQLが記載できます :v: 

```kotlin:src/main/kotlin/com/example/mybatisdemo/domain/mapper/TodoMapper.kt
package com.example.mybatisdemo.mapper

import com.example.mybatisdemo.domain.Todo
import org.apache.ibatis.annotations.Insert
import org.apache.ibatis.annotations.Mapper
import org.apache.ibatis.annotations.Options
import org.apache.ibatis.annotations.Select

@Mapper
interface TodoMapper {

    @Insert("""
        INSERT INTO todo
            (title, details, finished)
        VALUES
            (#{title}, #{details}, #{finished})
    """)
    @Options(useGeneratedKeys = true, keyProperty = "id")
    fun insert(todo: Todo)

    @Select("""
        SELECT
            id, title, details, finished
        FROM
            todo
        WHERE
            id = #{id}
    """)
    fun select(id: Int): Todo

}
```

# 開発プロジェクトの作成

今回も「[SPRING INITIALIZR](https://start.spring.io/)」を使います。ウィザードに以下の値を入力して「Generate Project」ボタンを押下すると、Mavenプロジェクトがダウンロードされます。なお、パッケージ名を変更するためには「More Options」をクリックする必要があります。

| 入力項目 | 値 | 備考 |
| --- | --- | --- | 
| Project | Maven Project | ※デフォルト |
| Language | **Kotlin** | ※デフォルト(Java)から変更 |
| Version | 2.1.4 | ※デフォルト |
| Group | com.example | ※デフォルト |
| Artifact | mybatis-kotlin-demo | ※デフォルト(demo)から変更 |
| Name | mybatis-kotlin-demo | ※デフォルト |
| Description | Demo project for Spring Boot | ※デフォルト |
| Package Name | com.example.mybatisdemo | ※デフォルト(com.example.mybatiskotlindemo)から変更 |
| Packaging | jar | ※デフォルト |
| Java Version | 8 | ※デフォルト |
| Dependencies | MyBatis, H2 |  |

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/117313/5ecd8d1a-4a25-8bc0-985e-d5425b5938b3.png)


任意のディレクトリにダウンロードしたZipファイルを解凍すれば、Mavenプロジェクトの出来上がりです。

# ドメインオブジェクトの作成

ドメインオブジェクトとして`Todo`クラスを作ります。

```kotlin:src/main/kotlin/com/example/mybatisdemo/domain/Todo.kt
package com.example.mybatisdemo.domain

class Todo {
    var id: Int = 0
    var title: String = ""
    var details: String? = null
    var finished: Boolean = false
}
```

# Mapperインターフェースの作成

`Todo`へのCRUD操作を提供するMapperインターフェースを作ります。

```kotlin:src/main/kotlin/com/example/mybatisdemo/domain/mapper/TodoMapper.kt
package com.example.mybatisdemo.mapper

import com.example.mybatisdemo.domain.Todo
import org.apache.ibatis.annotations.Insert
import org.apache.ibatis.annotations.Mapper
import org.apache.ibatis.annotations.Options
import org.apache.ibatis.annotations.Select

@Mapper
interface TodoMapper {

    @Insert("""
        INSERT INTO todo
            (title, details, finished)
        VALUES
            (#{title}, #{details}, #{finished})
    """)
    @Options(useGeneratedKeys = true, keyProperty = "id")
    fun insert(todo: Todo)

    @Select("""
        SELECT
            id, title, details, finished
        FROM
            todo
        WHERE
            id = #{id}
    """)
    fun select(id: Int): Todo

}
```

# todoテーブルの作成

H2の組み込みデータベースにtodoテーブルを作成します。
Spring Bootの自動コンフィギュレーションで作成される`DataSource`を使う場合は、クラスパス直下に`schema.sql`と`data.sql`というファイルを配置しておくと、Spring Boot起動時にこれらのファイルを読み込んでSQLを実行してくれます。

```sql:src/main/resources/schema.sql
CREATE TABLE todo (
    id IDENTITY
    ,title TEXT NOT NULL
    ,details TEXT
    ,finished BOOLEAN NOT NULL
);
```

# MybatisKotlinDemoApplicationの修正とSpring Bootアプリケーションの起動

`MybatisKotlinDemoApplication`を修正し、Mapperインタフェースを経由してデータベースにアクセスします。


```kotlin:src/main/kotlin/com/example/mybatisdemo/MybatisKotlinDemoApplication.kt
package com.example.mybatisdemo

import com.example.mybatisdemo.domain.Todo
import com.example.mybatisdemo.mapper.TodoMapper
import org.springframework.boot.CommandLineRunner
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.runApplication
import org.springframework.transaction.annotation.Transactional

@SpringBootApplication
class MybatisKotlinDemoApplication (private val todoMapper: TodoMapper) : CommandLineRunner {

    @Transactional
    override fun run(vararg args: String?) {
        val newTodo: Todo = Todo()
        newTodo.title = "飲み会"
        newTodo.details = "銀座 19:00"

        todoMapper.insert(newTodo) // 新しいTodoをインサートする

        val loadedTodo: Todo = todoMapper.select(newTodo.id) // インサートしたTodoを取得して標準出力する
        println("ID       : " + loadedTodo.id)
        println("TITLE    : " + loadedTodo.title)
        println("DETAILS  : " + loadedTodo.details)
        println("FINISHED : " + loadedTodo.finished)
    }

}

fun main(args: Array<String>) {
    runApplication<MybatisKotlinDemoApplication>(*args)
}
```

`MybatisKotlinDemoApplication`を修正したら、Spring Bootアプリケーションとして起動します。

```text:コンソール
$ ./mvnw spring-boot:run
...
2019-05-03 14:25:17.711  INFO 32402 --- [           main] c.e.m.MybatisKotlinDemoApplicationKt     : Started MybatisKotlinDemoApplicationKt in 1.919 seconds (JVM running for 13.144)
ID       : 1
TITLE    : 飲み会
DETAILS  : 銀座 19:00
FINISHED : false
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 11.610 s
[INFO] Finished at: 2019-05-03T14:25:17+09:00
[INFO] Final Memory: 55M/606M
[INFO] ------------------------------------------------------------------------
2019-05-03 14:25:18.133  INFO 32402 --- [       Thread-2] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Shutdown initiated...
2019-05-03 14:25:18.137  INFO 32402 --- [       Thread-2] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Shutdown completed.
```

お〜MyBatisと連携できましたね :clap: :clap: :clap: 

# JUnit上でMybatisKotlinDemoApplicationを起動

ダウンロードした開発プロジェクトには、JUnit用のテストケースクラス(`MybatisKotlinDemoApplicationTests`)が格納されています。せっかくなので、`MybatisKotlinDemoApplicationTests`を以下のように修正してテスト結果をassertしてみます。

```kotlin:src/test/kotlin/com/example/mybatisdemo/MybatisKotlinDemoApplicationTests.kt
package com.example.mybatisdemo

import org.hamcrest.Matchers.containsString
import org.junit.ClassRule
import org.junit.Test
import org.junit.runner.RunWith
import org.springframework.boot.test.context.SpringBootTest
import org.springframework.boot.test.rule.OutputCapture
import org.springframework.test.context.junit4.SpringRunner

@RunWith(SpringRunner::class)
@SpringBootTest
class MybatisKotlinDemoApplicationTests {

    companion object {
        @ClassRule @JvmField val out = OutputCapture()
    }

    @Test
    fun contextLoads() {
        out.expect(containsString("ID       : 1"))
        out.expect(containsString("TITLE    : 飲み会"))
        out.expect(containsString("DETAILS  : 銀座 19:00"))
        out.expect(containsString("FINISHED : false"))
    }

}
```

再度テストを実行すると、標準出力した内容が正しいことが検証できました :clap: :clap: :clap:

```text:コンソール
$ ./mvnw clean test
...
2019-05-03 14:27:36.468  INFO 32460 --- [           main] c.e.m.MybatisKotlinDemoApplicationTests  : Started MybatisKotlinDemoApplicationTests in 1.477 seconds (JVM running for 2.456)
ID       : 1
TITLE    : 飲み会
DETAILS  : 銀座 19:00
FINISHED : false
[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 2.516 s - in com.example.mybatisdemo.MybatisKotlinDemoApplicationTests
2019-05-03 14:27:36.832  INFO 32460 --- [       Thread-2] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Shutdown initiated...
2019-05-03 14:27:36.841  INFO 32460 --- [       Thread-2] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Shutdown completed.
[INFO] 
[INFO] Results:
[INFO] 
[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0
[INFO] 
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 11.338 s
[INFO] Finished at: 2019-05-03T14:27:37+09:00
[INFO] Final Memory: 46M/566M
[INFO] ------------------------------------------------------------------------
```

# まとめ

Kotlin上でも問題なくMyBatisが使えることと、アノテーションにSQLを書いても可読性が下がらないことが確認できました。KotlinはJavaに置き換わる言語として期待・注目されているみたいなので、わたしも勉強しようと思っています。（本は買ったけどまだ読んでない :sweat_smile: ）

なお、本投稿で紹介したプロジェクトの完成品は、GitHubで公開しています。

* https://github.com/kazuki43zoo/qiita-materials/tree/master/mybatis-spring-boot/mybatis-kotlin-demo

# 補足

Kotlinは超初心者なので、へんなところあるかもしれません（あしからず・・・）

# 参考サイト

* http://blog.64p.org/entry/2016/07/20/003013