Kotlin製のO/Rマッパー Exposedを試す

# Exposedとは？
- JetBrains社が開発しているKotlin製のO/Rマッパー
- DSL（SQLライク）、DAO（軽量）の2つのアクセス方法がある

# build.gradle.ktsへの追加

```kotlin
dependencies {
    implementation("org.jetbrains.exposed", "exposed-core", "0.39.2")
    implementation("org.jetbrains.exposed", "exposed-dao", "0.39.2")
    implementation("org.jetbrains.exposed", "exposed-jdbc", "0.39.2")
    implementation("mysql:mysql-connector-java:8.0.30")
}
```

# DBの準備

## docker-compose.ymlファイルをプロジェクト直下に置く

```yml:docker-compose.yml
version: '3'

services:
  # MySQL
  db:
    image: mysql:8.0.30
    ports:
      - 3306:3306
    container_name: mysql_host
    environment:
      MYSQL_ROOT_PASSWORD: mysql
    command: mysqld --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci
    volumes:
      - ./docker/db/data:/var/lib/mysql
      - ./docker/db/my.cnf:/etc/mysql/conf.d/my.cnf
      - ./docker/db/sql:/docker-entrypoint-initdb.d
```

- dockerコンテナの起動

```sh
docker-compose up -d
```

## create database 、 create table 

```sql
create database exposed_example;

use exposed_example;

create table member(
    id int NOT NULL AUTO_INCREMENT,
    name varchar(32) NOT NULL,
    PRIMARY KEY (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

```

# DSLでデータ操作を実装する

- Tableクラスを継承して、テーブルに対応したオブジェクトを作成する
- Database.connectでDB接続する
- transactionでトランザクションを定義する
- Member.insertでデータを挿入する
    - getオペレータで登録結果から指定のカラムの値を取得できる
- Member.selectで任意のレコードを取得できる
    - member[Member.name]のようにmapとして値を取り出す

```kotlin
import org.jetbrains.exposed.sql.*
import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq
import org.jetbrains.exposed.sql.transactions.transaction

// Tableクラスを継承したmemberテーブルに対応するMemberオブジェクトを作成する
object Member : Table("member") {
    // カラム情報を同じにする
    val id = integer("id").autoIncrement()
    val name = varchar("name", 32)
}

fun main() {
    // DBへの接続
    Database.connect(
        url = "jdbc:mysql://127.0.0.1:3306/exposed_example",
        driver = "com.mysql.cj.jdbc.Driver",
        user = "root",
        password = "mysql"
    )
    // トランザクションの定義
    transaction {
        // ログの追加（任意）
        addLogger(StdOutSqlLogger)

        // データを挿入する
        val id = Member.insert {
            it[name] = "Kotlin"
        } get Member.id // getオペレータで、登録結果から指定のカラムの値を取得できる
        println("Inserted id: $id")

        // idが一致する1行を取得する
        val member: ResultRow = Member.select(Member.id eq id).single()
        // mapとして取り出す
        println("id: ${member[Member.id]}")
        println("name: ${member[Member.name]}")
    }
}
```

- 出力結果

```
SQL: INSERT INTO `member` (`name`) VALUES ('Kotlin')
Inserted id: 1
SQL: SELECT `member`.id, `member`.`name` FROM `member` WHERE `member`.id = 1
id: 1
name: Kotlin
```

# DAOでデータ操作を実装する

- IntIdTableクラスを継承したmemberテーブルに対応するMemberTableオブジェクトを作成する
    - idを除いたカラム情報を同じにする
- IntEntityクラスを継承したMemberEntityクラスを作成する
    - 主キー以外のカラムは、対応するカラムのフィールドにデリゲートする
- newでINSERT文の実行。登録後の値が設定されたEntityを返す
- findByでSELECT文の実行。null許容のMemberEntityが返却される

```kotlin
import org.jetbrains.exposed.dao.IntEntity
import org.jetbrains.exposed.dao.IntEntityClass
import org.jetbrains.exposed.dao.id.EntityID
import org.jetbrains.exposed.dao.id.IntIdTable
import org.jetbrains.exposed.sql.Database
import org.jetbrains.exposed.sql.StdOutSqlLogger
import org.jetbrains.exposed.sql.addLogger
import org.jetbrains.exposed.sql.transactions.transaction

// IntIdTableクラスを継承したmemberテーブルに対応するMemberTableオブジェクトを作成する
object MemberTable : IntIdTable("member") {
    // idを除いたカラム情報を同じにする
    val name = varchar("name", 32)
}

// IntEntityクラスはEntityクラスの子クラスでint型の主キーを持つテーブルで使用する
// EntityはSQLの生成などがラッピングされた関数を持つ
class MemberEntity(id: EntityID<Int>) : IntEntity(id) {
    companion object : IntEntityClass<MemberEntity>(MemberTable)

    // 主キー以外のカラムは、対応するカラムのフィールドにデリゲートする
    var name by MemberTable.name
}

fun main() {
    // DBへの接続
    Database.connect(
        url = "jdbc:mysql://127.0.0.1:3306/exposed_example",
        driver = "com.mysql.cj.jdbc.Driver",
        user = "root",
        password = "mysql"
    )
    // トランザクションの定義
    transaction {
        // ログの追加（任意）
        addLogger(StdOutSqlLogger)

        // INSERT文の実行　登録後の値が設定されたEntityを返す
        val member = MemberEntity.new {
            name = "Kotlin"
        }
        println("Inserted id: ${member.id}")

        // 主キー検索する。null許容のMemberEntityクラスが返却される
        MemberEntity.findById(member.id)?.let {
            println("id: ${it.id}")
            println("name: ${it.name}")
        }
    }
}
```

- 出力結果

```
SQL: INSERT INTO `member` (`name`) VALUES ('Kotlin')
Inserted id: 2
id: 2
name: Kotlin
```

### 参考

Kotlin サーバーサイドプログラミング実践開発   竹端 尚人
https://www.amazon.co.jp/dp/B091PGXHJY/ref=cm_sw_r_tw_dp_ZAWZHH6DFBC5P3H0P38D