Micronaut + Kotlin のプロジェクトにてMyBatisを使ってデータベースからデータ取得してみる

# 前回
[Micronaut + KotlinでHelloWorld](https://qiita.com/h-ueno2/items/c2c700eb7ce72f382a4b)
前回のプロジェクトを拡張し、MyBatisを使ってデータベース接続をしてみます。

実際に作成したリポジトリは[こちら](https://github.com/h-ueno2/micronaut_sample)になります。

# データベースを用意

DockerでPostgreSQLのコンテナを用意します。
前回作成したプロジェクトと同階層に以下を作成しました。

* DB環境作成用の`docker-compose.yml`

```yml:docker-compose.yml
version: '3'
services: 
  db:
    image: postgres
    restart: always
    container_name: postgres
    environment: 
      POSTGRES_USER: postgres
      POSTGRES_PASSWORD: postgres
      POSTGRES_DB: books
    volumes:
      - ./data/postgres/db-data:/var/lib/postgresql/data
      - ./database:/docker-entrypoint-initdb.d
    ports:
      - 5432:5432
```

* テーブル作成用のSQLを格納する`data`フォルダとSQL

```sql:database/init.sql
set client_encoding = 'UTF8';

CREATE TABLE book (
  id SERIAL,
  name VARCHAR(200) NOT NULL,
  publisher VARCHAR(200),
  publication_date DATE,
  created_at TIMESTAMP NOT NULL,
  updated_at TIMESTAMP NOT NULL,
  CONSTRAINT pk_book PRIMARY KEY (id)
);

-- 確認用の初期データ
INSERT INTO book (id, name, publisher, publication_date, created_at, updated_at) VALUES (1, 'テスト書籍1', 'テスト出版社A', null, current_timestamp, current_timestamp);
INSERT INTO book (id, name, publisher, publication_date, created_at, updated_at) VALUES (2, 'テスト書籍2', 'テスト出版社A', null, current_timestamp, current_timestamp);
```

* DBデータをGitの管理対象から外すための`.gitignore`

```:.gitignore
data
```

`docker-compose`のあるフォルダに移動し、コンテナを起動します。

```shell
$ docker-compose up -d
```

これでDBの方の準備はOKです。

# プロジェクトの改修

## ライブラリの追加
build.gradleを編集し、ライブラリを追加します。

```gradle:server/build.gradle
dependencies {
    // 省略
    implementation("org.mybatis:mybatis:3.4.6")
    implementation("io.micronaut.sql:micronaut-jdbc-hikari")
    runtimeOnly("org.postgresql:postgresql")
    // 省略
}
```

## 接続設定とか追加

`application.yml`を編集します。

* 接続先の追加
* MyBatisの設定を追加

```yml:server/src/main/resources/application.yml
micronaut:
  application:
    name: helloworld
# 以下を追加
datasources:
  default:
    url: jdbc:postgresql://localhost:5432/books
    username: postgres
    password: postgres
    driverClassName: org.postgresql.Driver
mybatis:
  # mapperのxmlファイルを格納する位置を指定
  mapper-locations: classpath:example/mapper/*.xml
  configuration:
    map-underscore-to-camel-case: true
    default-fetch-size: 100
    default-statement-timeout: 30
    cache-enabled: false
```

## モデルの作成
DBから取得したデータを格納するクラスを作成します。

```kotlin:server/src/main/kotlin/example/model/Book.kt
package example.model

import io.micronaut.core.annotation.Introspected
import java.sql.Date

/**
 * 書籍データクラス
 */
@Introspected
data class Book(
        var id: Int?,
        var name: String,
        var publisher: String?,
        var publicationDate: Date?,
)
```

## SqlSessionFactoryの作成

SqlSessionを用意するFactoryクラスを作成します。

```kotlin:server/src/main/kotlin/example/MybatisFactory.kt
package example

import io.micronaut.context.annotation.Factory
import org.apache.ibatis.mapping.Environment
import org.apache.ibatis.session.Configuration
import org.apache.ibatis.session.SqlSessionFactory
import org.apache.ibatis.session.SqlSessionFactoryBuilder
import org.apache.ibatis.transaction.TransactionFactory
import org.apache.ibatis.transaction.jdbc.JdbcTransactionFactory
import javax.inject.Singleton
import javax.sql.DataSource

@Factory
class MybatisFactory(private val dataSource: DataSource) {

    @Singleton
    fun sqlSessionFactory(): SqlSessionFactory {
        val transactionFactory: TransactionFactory = JdbcTransactionFactory()

        val environment = Environment("dev", transactionFactory, dataSource)
        val configuration = Configuration(environment)
        // mapperをスキャンするパッケージを指定。
        configuration.addMappers("example")

        return SqlSessionFactoryBuilder().build(configuration)
    }
}
```

## Mapperの作成

SQLとのインタフェースとなるMapperを作成します。

```kotlin:server/src/main/kotlin/example/mapper/BookMapper.kt
package example.mapper

import example.model.Book
import org.apache.ibatis.annotations.Mapper

@Mapper
interface BookMapper {
    fun findById(id: Int): Book
}
```

実行するSQL本体はXMLに記載します。

```xml:server/src/main/resources/example/mapper/BookMapper.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="example.mapper.BookMapper">

    <select id="findById" resultType="example.model.Book">
        SELECT
        id,
        name,
        publisher,
        publication_date
        FROM book
        <where>
            id = #{id}
        </where>
    </select>

</mapper>
```

インタフェースの実装としてServiceクラスを作成します。

```kotlin:server/src/main/kotlin/example/service/BookService.kt
package example.service

import example.mapper.BookMapper
import example.model.Book
import org.apache.ibatis.session.SqlSessionFactory
import javax.inject.Singleton

@Singleton
class BookService(private val sqlSessionFactory: SqlSessionFactory) : BookMapper {

    override fun findById(id: Int): Book {
        sqlSessionFactory.openSession().use { session ->
            val bookMapper = session.getMapper(BookMapper::class.java)
            return bookMapper.findById(id)
        }
    }
}
```

## Controllerの作成

データベースにアクセスするAPIを作成します。

```kotlin:server/src/main/kotlin/example/controller/BookController.kt
package example.controller

import example.mapper.BookMapper
import example.model.Book
import io.micronaut.http.HttpResponse
import io.micronaut.http.annotation.Controller
import io.micronaut.http.annotation.Get


@Controller()
class BookController(private val bookMapper: BookMapper) {

    /**
     * 書籍情報の取得。
     * ID指定
     *
     * @param id 書籍ID
     *
     * @return HttpResponse
     */
    @Get("/book/{id}")
    fun readById(id: Int): HttpResponse<Book> {
        return HttpResponse.ok(bookMapper.findById(id))
    }
}
```

# ひとまず完成

ここまで`/book/{id}`のエンドポイントにアクセスすればデータを取得する動きができました。
試してみます。

サーバを実行します。

```shell
$ cd server 
$ ./gradlew run
```

`http://localhost:8080/book/1`にアクセスします。

```shell
$ curl -X GET  http://localhost:8080/book/1
{"id":1,"name":"テスト書籍1","publisher":"テスト出版社A"}
```

データベースからデータを取得することができました。

# 次にやりたいこと

登録、更新も作りました。
[Micronaut + Kotlin のプロジェクトにてMyBatisを使ってデータベースにデータ登録してみる](https://qiita.com/h-ueno2/items/a362e2beb5887b915bd1)

# 参考
[Access a database with MyBatis](https://guides.micronaut.io/micronaut-data-access-mybatis/guide/index.html)