Kotlin + Spring Boot + Gradle にFlywayを導入する


# はじめに
Kotlin + Spring Boot + Gradleで作成したアプリにマイグレーションツールであるFlywayを導入する手順を説明します。
なおデータベースにはMySQLを用い、Docker上に立てます。

## 1. プロジェクトの作成

[Spring initializr](https://start.spring.io/)では以下のように設定しました
![スクリーンショット 2022-02-06 16.18.17.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1265757/f8aa4ab5-b6a6-5f1f-6a88-4138c76d3579.png)

## 2. DockerでMySQLのDBを生成

プロジェクトのルート配下に以下のような構成でDocker用のディレクトリとファイルを作成

```
.
├── docker/
    ├── db/
    └── docker-compose.yml

```
docker-compose.ymlの中身

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

dockerディレクトリに移動

```bash
$ cd docker
```

コンテナを立ち上げる

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

正常に立ち上げっているか確認

```
$ docker ps
CONTAINER ID   IMAGE          COMMAND                  CREATED       STATUS       PORTS                                                  NAMES
69bb5c657441   mysql:8.0.22   "docker-entrypoint.s…"   10 seconds ago   Up 10 hours   0.0.0.0:3306->3306/tcp, :::3306->3306/tcp, 33060/tcp   mysql
```

コンテナ内に入る

```
$ docker exec -it mysql /bin/bash
```

DBにアクセス

```
mysql -h 127.0.0.1 --port 3306 -uroot -pmysql
```

exampleという名前のデータベースを作成

```
mysql> create database example;
```

## 3. Flyway導入

プロジェクトとMySQLの接続情報設定のためapplication.ymlを以下のように記述

```yml:application.yml
spring:
  datasource:
    url: jdbc:mysql://127.0.0.1:3306/example?characterEncoding=utf8
    username: root
    password: mysql
    driverClassName: com.mysql.jdbc.Driver
```

build.gradle.ktsにプラグイン、依存関係を追加

```Kotlin:build.gradle.kts
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile

plugins {
	id("org.springframework.boot") version "2.6.3"
	id("io.spring.dependency-management") version "1.0.11.RELEASE"

	id("org.flywaydb.flyway") version "8.2.0" // 追加

	kotlin("jvm") version "1.6.10"
	kotlin("plugin.spring") version "1.6.10"
}

group = "com.example"
version = "0.0.1-SNAPSHOT"
java.sourceCompatibility = JavaVersion.VERSION_11

repositories {
	mavenCentral()
}

dependencies {
	implementation("org.springframework.boot:spring-boot-starter-web")
	implementation("com.fasterxml.jackson.module:jackson-module-kotlin")
	implementation("org.jetbrains.kotlin:kotlin-reflect")
	implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")

	implementation("mysql:mysql-connector-java:8.0.23") // 追加
    implementation("org.flywaydb:flyway-core") // 追加

	testImplementation("org.springframework.boot:spring-boot-starter-test")
}

tasks.withType<KotlinCompile> {
	kotlinOptions {
		freeCompilerArgs = listOf("-Xjsr305=strict")
		jvmTarget = "11"
	}
}

tasks.withType<Test> {
	useJUnitPlatform()
}

// 追加
flyway {
	url = "jdbc:mysql://127.0.0.1:3306/example"
	user = "root"
	password = "mysql"
}
```

## 4. マイグレーションファイルを作成
src > main > resources 配下に db > migration ディレクトリを作成し、migrationディレクトリ配下にマイグレーション用の拡張子(.sql)のファイルを作成する。
今回は　`V1.0.0__user.sql`というファイルを作成。
このファイルにマイグレーションした時に実行されるSQLを記述します。
※ファイル名は`V<バージョン>__<任意の名前>.sql`のように命名規則があります。

```V1.0.0__user.sql
CREATE TABLE user (
  id bigint NOT NULL,
  email varchar(256) UNIQUE NOT NULL,
  password varchar(128) NOT NULL,
  name varchar(32) NOT NULL,
  role_type enum('ADMIN', 'USER'),
  PRIMARY KEY (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
```

## マイグレーションを実行

flywayのtaskが実行できるか確認

```
$ ./gradlew tasks
```

出力されたログの一覧に以下のような記載があれば実行可能

```
...省略

Flyway tasks
------------
flywayBaseline - Baselines an existing database, excluding all migrations up to and including baselineVersion.
flywayClean - Drops all objects in the configured schemas.
flywayInfo - Prints the details and status information about all the migrations.
flywayMigrate - Migrates the schema to the latest version.
flywayRepair - Repairs the Flyway schema history table.
flywayUndo - Undoes the most recently applied versioned migration. Flyway Teams only.
flywayValidate - Validate applied migrations against resolved ones (on the filesystem or classpath) to detect accidental changes that may prevent the schema(s) from being recreated exactly. Validation fails if differences in migration names, types or checksums are found, versions have been applied that aren"t resolved locally anymore or versions have been resolved that haven"t been applied yet

...省略

```

`flywayMigrate`でマイグレーションの実行。BUILD SUCCESSFUL のログが出れば成功


```
./gradlew flywayMigrate
```

ちゃんとテーブルが作成されてるか確認
以下のようになれば成功。

```
mysql> use example;
省略

mysql> show tables;
+------------------------------+
| Tables_in_attendance_manager |
+------------------------------+
| flyway_schema_history        |
| user                         |
+------------------------------+

mysql> show columns from user;
+-----------+----------------------+------+-----+---------+-------+
| Field     | Type                 | Null | Key | Default | Extra |
+-----------+----------------------+------+-----+---------+-------+
| id        | bigint               | NO   | PRI | NULL    |       |
| email     | varchar(256)         | NO   | UNI | NULL    |       |
| password  | varchar(128)         | NO   |     | NULL    |       |
| name      | varchar(32)          | NO   |     | NULL    |       |
| role_type | enum('ADMIN','USER') | YES  |     | NULL    |       |
+-----------+----------------------+------+-----+---------+-------+

```

# ハマりポイント
MySQLとFlywayプラグインのバージョンに相性があるようです。
相性が悪いと、マイグレーション実行時にエラーになります。

## 参考
- [Fryway ドキュメント](https://flywaydb.org/documentation/getstarted/firststeps/gradle)
- [データベースマイグレーションにFlyway使ってみた](https://ver-1-0.net/2017/07/10/flyway-migration)
- [Spring Bootでflywayを使ってみた。](https://qiita.com/gp333/items/347a446ad183dda36da3)
- [FlywayでSpringBootプロジェクトのDBマイグレーション](https://springhack.com/flyway%E3%81%A7springboot%E3%83%97%E3%83%AD%E3%82%B8%E3%82%A7%E3%82%AF%E3%83%88%E3%81%AEdb%E3%83%9E%E3%82%A4%E3%82%B0%E3%83%AC%E3%83%BC%E3%82%B7%E3%83%A7%E3%83%B3/#FlywaySpringBoot)