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

# 前回
[Micronaut + Kotlin のプロジェクトにてMyBatisを使ってデータベースからデータ取得してみる](https://qiita.com/h-ueno2/items/9b70e55b341e4a3dd355)

前回のプロジェクトを拡張し、データベースにデータ登録してみたいと思います。
実際に作成したリポジトリは[こちら](https://github.com/h-ueno2/micronaut_sample)になります。


# Mapper
## Interfaceの作成

BookMapper.ktに次のメソッドの宣言を追加します。

* `insert`：データの登録（Insert文の実行）を行います。
* `update`：データの更新（Update文の実行）を行います。

```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 findAll(): List<Book>
    fun findById(id: Int): Book
    fun insert(book: Book)
    fun update(book: Book)
}
```

## SQLの作成

実行するSQL本体を定義します。
今回のサンプルではBookMapper.xmlに定義します。

```xml:server/src/main/resources/example/mapper/BookMapper.xml
    <insert id="insert" parameterType="example.model.Book">
        INSERT INTO book (
            name,
            publisher,
            publication_date,
            created_at,
            updated_at
        ) VALUES (
            #{name},
            #{publisher},
            #{publicationDate},
            current_timestamp,
            current_timestamp
        );
    </insert>

    <update id="update" parameterType="example.model.Book">
        UPDATE book
        SET
            name = #{name},
            publisher = #{publisher},
            publication_date = #{publicationDate},
            updated_at = current_timestamp
        WHERE
            id = #{id}
    </update>
```

## 実装部分の作成

実装部分としてServiceクラスにもメソッドを追加します。

* `insert(book: Book)`
* `update(book: Book)`

```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 findAll(): List<Book> {
        sqlSessionFactory.openSession().use { session ->
            val bookMapper = session.getMapper(BookMapper::class.java)
            return bookMapper.findAll()
        }
    }

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

    override fun insert(book: Book) {
        sqlSessionFactory.openSession().use { session ->
            val bookMapper = session.getMapper(BookMapper::class.java)
            bookMapper.insert(book)
            session.commit()
        }
    }

    override fun update(book: Book) {
        sqlSessionFactory.openSession().use { session ->
            val bookMapper = session.getMapper(BookMapper::class.java)
            bookMapper.update(book)
            session.commit()
        }
    }

}
```

# Controller

データベースにアクセスするAPIを作成します。
Mapperと同様に登録と更新用の口を作成します。

* `create(book: Book)`：登録用のAPI。`/book`にPOSTでリクエストすることで実行。
* `update(book: Book)`：更新用のAPI。`/book`にPUTでリクエストすることで実行。

```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
import io.micronaut.http.annotation.Post
import io.micronaut.http.annotation.Put


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

    /**
     * 書籍情報の全取得。
     *
     * @return HttpResponse
     */
    @Get("/book")
    fun read(): HttpResponse<List<Book>> {
        return HttpResponse.ok(bookMapper.findAll())
    }

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

    /**
     * 書籍情報の登録。
     */
    @Post("/book")
    fun create(book: Book): HttpResponse<String> {
        bookMapper.insert(book)
        return HttpResponse.ok()
    }

    /**
     * 書籍情報の更新。
     */
    @Put("/book")
    fun update(book: Book): HttpResponse<String> {
        bookMapper.update(book)
        return HttpResponse.ok()
    }
}
```

# Model

モデルも若干修正します。
前回のままだとJSONが一部の項目で正しくマッピングできなかったので、`JsonProperty`のアノテーションを追加します。

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

import com.fasterxml.jackson.annotation.JsonProperty
import io.micronaut.core.annotation.Introspected
import java.sql.Date


/**
 * 書籍データクラス
 */
@Introspected
data class Book(
        @JsonProperty("id")
        var id: Int?,

        @JsonProperty("name")
        var name: String,

        @JsonProperty("publisher")
        var publisher: String?,

        @JsonProperty("publication_date")
        var publicationDate: Date?,
)
```

# ひとまず完成

ここまでで書籍情報の登録、更新するAPIが作成されました。
試してみましょう。

サーバを実行します。

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

今回、APIへのリクエストには[POSTMAN](https://www.postman.com/)を使います。

まず登録してみます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/83511/d658deca-1ca1-e77e-2a59-bb5d35ef1618.png)

ドン！
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/83511/be6c54d8-61d6-4272-6863-21440b40b743.png)

登録できましたね！

次、更新を試してみます。
`name`を`test書籍3`にしてみます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/83511/1ccc17c5-ece4-34bf-0f3e-5a26729a3b41.png)
ドン！
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/83511/4a23ee2e-9d6b-7319-df9b-30eb0f2f722c.png)
更新できました！

# おわりに

今回追加した`insert`も`update`も`BookService`内にて各SQL実行後に`session.commit()`をしています。
参考にした[ガイド](https://guides.micronaut.io/micronaut-data-access-mybatis/guide/index.html)でも同じように各SQLの実行後に`session.commit()`を実行しトランザクションを確定させています。
ただ、個人的にはメソッド単位でトランザクションを確定させるのは使い勝手が悪いようにも感じます。
例えば、実業務で開発するサービスでは1リクエストの中で複数のテーブル更新もすることがあるかと思います。
できればAPI単位でトランザクションを持ちたい所です。
Spring Bootだと記事が見つかるけど、Micronautだとどうやるのだろうと調査中。。

# 次にやりたいこと

* DB周りの自動テストを作りたい。何を使ってやるのがデファクトなんでしょうか…？
* React勉強し始めたので、フロント作ってみようかな。


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