以前「GroovyでMyBatisのMapperをつくる(とSQLの可読性がGood!!)」という投稿をしましたが、今回は最近話題?のKotlin上でMaBatis(mybatis-spring-boot-starter)を使ってみました。
なお、題材は以前投稿した「mybatis-spring-boot-starterの使い方」と一緒です。
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 (本投稿はIDEなし前提ですが・・・)
Kotlin版でのアプローチ
Groovy版は主言語はあくまでJavaで、Javaアノテーションの表現の貧弱なところをGroovyの複数行文字列(いわゆる「ヒアドキュメント」に分類される仕組み)を利用することで解消するというアプローチでした。Kotlin版は、主言語自体をKotlinとして、Kotlinの中からMyBatis(mybatis-spring-boot-starter)を利用するというアプローチになっています。
Kotlinの複数行文字列(ヒアドキュメント!?)を使う
Groovy版と同様に、SQLはKotlinのアノテーションを使って指定します。Kotlinのアノテーションを利用すると以下のような感じでSQLが記載できます
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」を使います。ウィザードに以下の値を入力して「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 |
任意のディレクトリにダウンロードしたZipファイルを解凍すれば、Mavenプロジェクトの出来上がりです。
ドメインオブジェクトの作成
ドメインオブジェクトとしてTodo
クラスを作ります。
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インターフェースを作ります。
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を実行してくれます。
CREATE TABLE todo (
id IDENTITY
,title TEXT NOT NULL
,details TEXT
,finished BOOLEAN NOT NULL
);
MybatisKotlinDemoApplicationの修正とSpring Bootアプリケーションの起動
MybatisKotlinDemoApplication
を修正し、Mapperインタフェースを経由してデータベースにアクセスします。
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アプリケーションとして起動します。
$ ./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と連携できましたね
JUnit上でMybatisKotlinDemoApplicationを起動
ダウンロードした開発プロジェクトには、JUnit用のテストケースクラス(MybatisKotlinDemoApplicationTests
)が格納されています。せっかくなので、MybatisKotlinDemoApplicationTests
を以下のように修正してテスト結果をassertしてみます。
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"))
}
}
再度テストを実行すると、標準出力した内容が正しいことが検証できました
$ ./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に置き換わる言語として期待・注目されているみたいなので、わたしも勉強しようと思っています。(本は買ったけどまだ読んでない )
なお、本投稿で紹介したプロジェクトの完成品は、GitHubで公開しています。
補足
Kotlinは超初心者なので、へんなところあるかもしれません(あしからず・・・)