ExposedのDB周りの実装について（Kotlin × Spring Boot

弊社は自社サービスの開発で現在Kotlin言語を利用してSpringBootでのアプリケーション開発を行っています。
なかなか文献が少ない構成ではあるので色々公開していきたいと思ってます。

今回はテーブル定義とdao周りの実装です

## 前提
言語はkotlin
DBはPostgres
org.jetbrains.exposedの0.38.2を利用。

https://github.com/JetBrains/Exposed

以下の構成を想定しての実装解説となります
![スクリーンショット 2022-11-30 20.33.36.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/323088/7cb3636c-1cb6-3b44-0bb6-aa7ce9ec57a6.png)


## 必要なパッケージ

以下のパッケージをgradleで定義
```gradle
implementation("org.jetbrains.exposed", "exposed-core", "0.38.2")
implementation("org.jetbrains.exposed", "exposed-dao", "0.38.2")
implementation("org.jetbrains.exposed", "exposed-jdbc", "0.38.2")
implementation("org.jetbrains.exposed", "exposed-jodatime", "0.38.2")
implementation("org.jetbrains.exposed", "exposed-java-time", "0.38.2")
implementation("org.jetbrains.exposed", "exposed-spring-boot-starter", "0.38.2")
implementation("org.jetbrains.exposed", "spring-transaction", "0.38.2")
``` 

## テーブル定義の実装
```kotlin
import org.jetbrains.exposed.dao.id.IntIdTable
import org.jetbrains.exposed.sql.jodatime.date
import org.jetbrains.exposed.sql.jodatime.datetime

object Users: IntIdTable("users") {
    var name = varchar("name", 50)
    var address = varchar("address", 255).nullable()
    var isActive = bool("is_active").default(false)
    var enteredOn = date("entered_on")
    var createdAt = datetime("created_at")
}
```

```kotlin
import org.jetbrains.exposed.dao.id.IntIdTable

object Orders: IntIdTable("orders") {
    var user = reference("user_id", Users)
    var code = integer("code")
}
```
[org.jetbrains.exposed.sql.Table](https://github.com/JetBrains/Exposed/blob/0.38.2/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/Table.kt) クラスに定義されているものを利用できます

また、datetimeやdateについては[別パッケージ](https://github.com/JetBrains/Exposed/blob/0.38.2/exposed-jodatime/src/main/kotlin/org/jetbrains/exposed/sql/jodatime/DateColumnType.kt)となっているようです

null / not nullの使い分けについてはどの型であっても `.nullable()` をつけてあげることで定義可能

リレーション表現については、1：1以上については `reference` を利用する形となり、
本記事では触れてませんが、1：0以上については `optReference` を利用します

## DAOクラスの実装
```kotlin
import org.jetbrains.exposed.dao.IntEntity
import org.jetbrains.exposed.dao.IntEntityClass
import org.jetbrains.exposed.dao.id.EntityID

class User(
        id: EntityID<Int>
): IntEntity(id) {
    companion object: IntEntityClass<User>(Users)
    var name by Users.name
    var address by Users.address
    var isActive by Users.isActive
    var enteredOn by Users.enteredOn
    var createdAt by Users.createdAt
}
```

```kotlin
import org.jetbrains.exposed.dao.IntEntity
import org.jetbrains.exposed.dao.IntEntityClass
import org.jetbrains.exposed.dao.id.EntityID

class Order(
        id: EntityID<Int>
): IntEntity(id) {
    companion object: IntEntityClass<Order>(Orders)
    var user by User referencedOn Orders.user
    var code by Orders.code
}
```
DAO側クラスについては基本的にはテーブルクラスのフィールドを見ているだけになります
>リレーション表現については、1：1以上については `reference` を利用する形となり、
>本記事では触れてませんが、1：0以上については `optReference` を利用します

こちらで触れたリレーション表現の仕方については
referenceは `referencedOn`
optReferenceは `optionalReferencedOn`
を利用することになります


## 最後に
テーブル定義とDAO定義それぞれやらないといけないので若干めんどくさいところもあるんですが、このようにしとくことによって使うときにコードがスリムになり可読性が向上するので是非やっといたほうが良いかなと思います。

また、かんたんなデータ取得はDAO形式、少し凝ったことをやりたくなったらDSLで書くなど逃げ道も用意できるので個人的には嬉しい感じ（もちろんビジネスロジック上で使い分けちゃうと良くないので隠蔽してあげる必要はあると思いますが）

せっかくテーブル定義があるのだから、マイグレーションもやっちゃいたいなと思ってましたが、現在Flyway使ってしまってます。
どうやらExposedでもできるようなので、今度是非試してみたいと思います

https://qiita.com/KenjiOtsuka/items/f5bae31f285ea1008d31