Doma2 を Spring boot + Kotlin で使う

# 前提
環境は表題の通り以下となります。

- spring boot 2.1.1
- kotlin 1.3.31
- doma 2.24.0
- Intellij

ORマッピングする元のDBは作成されているものとします。

## Doma2 + Kotlin
[こちら](https://doma.readthedocs.io/en/stable/kotlin-support/)にある通り、「Domaは [Kotlin](https://kotlinlang.org/) 1.3.11以上を**実験的に**サポートしています。」とのことなので、自己責任で導入をお願いします。
私が使っている限りでは、いまのところ不具合等は起きていません。

## Kotlinで使うと何が良いか？
データをマッピングする[Entityクラス](https://doma.readthedocs.io/en/stable/entity/)を [data class](https://dogwood008.github.io/kotlin-web-site-ja/docs/reference/data-classes.html) として定義できるため色々便利なメソッドが使えたり、NOT NULL なカラムを Entityでは NonNull のプロパティとして定義できたりしてより安全な運用ができます。
特に immutable として Entity を扱う場合はデータの一部を変更してデータをコピーできる copy() メソッドなどは重宝するのではないでしょうか。

私は doma2 の Entity をそのまま使わず 別途定義したクラスにマッピングしているので、単純に java を使わずに実装したかっという気持ちが強いです。

# 実装
## build.gradle

```build.gradle
buildscript {
  ext {
    springBootVersion = '2.1.1.RELEASE'
    kotlinVersion = '1.3.31'
    mysqlConnectorVersion = '8.0.13'
    doma_version = '2.24.0'
  }
  repositories {
    mavenCentral()
  }
  dependencies {
    classpath("org.springframework.boot:spring-boot-gradle-plugin:$springBootVersion")
    classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion")
    classpath("org.jetbrains.kotlin:kotlin-allopen:$kotlinVersion")
    classpath("mysql:mysql-connector-java:$mysqlConnectorVersion")
  }
}

apply plugin: 'kotlin-kapt'

kapt {
  arguments {
    arg("doma.resources.dir", compileKotlin.destinationDir)
  }
}

dependencies {
  implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"

  kapt "org.seasar.doma:doma:$doma_version"
  implementation "org.seasar.doma:doma:$doma_version"
  implementation group: 'org.seasar.doma.boot', name: 'doma-spring-boot-starter', version: '1.1.1'
  implementation group: 'mysql', name: 'mysql-connector-java', version: mysqlConnectorVersion
}

task copyDomaResources(type: Sync) {
  from sourceSets.main.resources.srcDirs
  into compileKotlin.destinationDir
  include 'doma.compile.config'
  include 'META-INF/**/*.sql'
  include 'META-INF/**/*.script'
}

compileKotlin {
  kotlinOptions.jvmTarget = "1.8"
  dependsOn copyDomaResources
}

processResources {
  filesMatching('**/application.yml') {
    filter(
        ReplaceTokens,
        tokens: [
            'datasourceUrl'     : "${datasourceUrl}".toString(),
            'datasourceUser'    : "${datasourceUser}".toString(),
            'datasourcePassword': "${datasourcePassword}".toString()
        ]
    )
  }
}

```

### doma-spring-boot-starter
- dependenciesに追加します。
- application.ymlに書いた設定をDIしてくれるannotationが使えるようになります。
- Daoで sql ファイルの生成が楽になります。
- 詳細は[こちら]([)

### apply plugin: 'kotlin-kapt'
* [kapt](https://kotlinlang.org/docs/reference/kapt.html)を使います
* 以下も必ず記述

```gradle
kapt {
  arguments {
    arg("doma.resources.dir", compileKotlin.destinationDir)
  }
}
```

### copyDomaResources
* Domaで使用する sql ファイルをコピーするタスク
* compileKotlinにタスクへの依存関係を記述します。

## Intellijの設定
### Compilerの Enable annotation processing を有効にする

以下の画像を参照。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/49308/116526ce-229f-f2da-d640-4f246e0f047e.png)


### ビルドをgradleに移譲する
- 教えてくれた人によるとIntellijのビルドはgradleの複雑な文法を理解してくれないらしいので、gradleにビルドを移譲しましょう。
- 移譲しないと copyDomaResources とか走りませんでした。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/49308/7d9bb248-acfc-b6de-0ee5-3bf86e27f182.png)

## application.yml
```application.yml
spring:
  profiles:
    active: local, development
  # MySQL接続情報
  datasource:
    url: @datasourceUrl@
    username: @datasourceUser@
    password: @datasourcePassword@
    hikari:
      connection-init-sql: SELECT 1

```

- DBへの接続情報を記載しています。
- @で括った箇所は適宜読み替えてください。

## Entity
```HogeEntity.kt
@Entity(immutable = true)
@Table(name = "hoge", schema = "test")
data class HogeEntity(
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "id")
    val id: Long? = null,

    @Column(name = "name")
    val name: String,

    @Column(name = "created_at")
    val createdAt: LocalDateTime
)

```

### data class
- Entityは data class として定義してください。

### @Entity
- Entityであることを示すアノテーション
- immutable = true にします(Entityのプロパティ値が変更不可になります)

### @Table
- どの schema のどの table かを指定しています。
- schema は指定がなければ defaultの schema が指定されます。

### @Id
- プライマリーキーであることを示します。

### @GeneratedValue
- **id**値を自動的に生成します。
- mysql の場合は `strategy = GenerationType.IDENTITY` を指定すればいいはず。
- DB 側で `AUTO INCREMENT` を指定する必要があります。

### @Column
- 対応するカラム名を指定します。
- NOT NULLじゃない場合はプロパティをnullableにしてください。

## Dao
```HogeEntityDao.kt
@ConfigAutowireable
@Dao
interface HogeEntityDao {

  @Select
  fun selectById(id: Long): HogeEntity

  @Insert
  fun insert(entity: HogeEntity): Result<HogeEntity>

	@BatchInsert
	fun insert(entities: List<HogeEntity>): BatchResult<HogeEntity>

  @Update
  fun update(entity: HogeEntity): Result<HogeEntity>

  @Delete
  fun delete(entity: HogeEntity): Result<HogeEntity>
}
```

### @ConfigAutowireable
- application.ymlで指定したdomaの設定をDIします。

### @Dao
- Daoであることを示します。
- これを指定することでコンパイル時に実装クラスが自動生成されます。

### @Select
- データ取得メソッドに対して付与します。名前のまんまSELECT相当。
- このアノテーションを付与すると、対となるsqlファイルがない場合にエラーになります。
- Intellij で  option + Enter すると以下のように生成することが出来ます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/49308/9000ed61-cc61-046e-d73a-fa61ea8b75ea.png)

### @Update, @Delete, @BatchInsert
- それぞれ、そのままの意味で使用することが出来ます。
- `@BatchInsert(sqlFile = true` にすると@Selectと同じように直接sql文が書けます。

## sql
```selectById.sql
SELECT *
FROM test.hoge
WHERE id = /* id */1;
```

### SELECT *
- 対象のEntityクラスにマッピングします。
- table = entityじゃないときは直接指定でマッピングもできます。こちらを参照→[SQL templates — Doma  documentation](https://doma.readthedocs.io/en/stable/sql/#embedded-variable-directive)

### /* id */1
- `/* */` でくくられたプロパティで後ろの値を置き換えます。
- この場合は `fun selectById(id: Long)` で定義されているパラメータ id の値になります。

# まとめ
様々なORMライブラリがありますが、今回はDoma2を使用してみました。
Sql文をそのまま書けることや、やりたいことに対して結構柔軟に記述できる(Daoのfunctionに@Sqlアノテーションで直接sql文を書けたりもする)ので悪くないなと思いました。
難点はKotlinが実験的に対応されていることと、Entityなどの自動生成ツールがないことでしょうか。DBからDaoとEntityを自動生成する domagen というのもあったのですが、こちらは少々癖が強かったりkotlinに対応していなかったので使用をやめました。

ベストなORMライブラリを見つけたい。