- 投稿日:2019-04-03T20:18:21+09:00
ことりんと一緒 Springもね - 10. データベースマイグレーション - Flyway for MySQL
概要 / 説明
ことりんと一緒 Springもね - 9. データベースマイグレーション - Flyway では、Flywayを利用したデータベースレイアウトのバージョン管理の方法を確認しました。
そこで利用したデータベースは、作業の簡略化のために H2 Database を使用し、アプリケーションの起動とともに利用できるインメモリデータベースの状態で実施しました。一方で実際にアプリケーションを稼働させる場合は、インメモリデータベースではなく、データベースサーバを立ち上げてドライバ経由で接続するケースがほとんどだと思います。
そこで、今回は MySQL をデータベースサーバとして起動し、それに対して Flyway を使用してみます。前提 / 環境
ランタイムバージョン
- Kotlin : 1.3.21
- SpringBoot : 2.1.1.RELEASE
Spring Dependencies
- Web
- JDBC
- JPA
- Actuator
- Flyway
開発環境
- OS : Mac
- IDE : IntelliJ IDEA
- Build : Gradle
手順 / 解説
MySQL サーバの準備
MySQLサーバは Docker コンテナを利用して簡単に用意します、
docker-compose.yml の用意
次の環境が起動するような
docker-compose.yml
を作成します。
項目 内容 MySQL バージョン 5.7 アクセスユーザ guest アクセスパスワード guest version: "3.7" services: db: image: mysql:5.7 container_name: my_db environment: MYSQL_ROOT_PASSWORD: root MYSQL_USER: guest MYSQL_PASSWORD: guest MYSQL_DATABASE: guest restart: always command: --default-authentication-plugin=mysql_native_password ports: - 3306:3306MySQL サーバの起動
docker-compose.yml
の配置場所、またはファイルを-f
オプションで指定して、次のコマンドで Docker コンテナとして MySQL を起動します。$ docker-compose up -dApplication 定義
以下のデータベース接続定義及びFlyway定義を追加します。
項目 設定値 JDBCドライバ com.mysql.cj.jdbc.Driver データベース接続URL jdbc:mysql://localhost:3306/app?autoReconnect=true&useSSL=false アクセスユーザ guest アクセスパスワード guest Flyway ベースラインバージョン 0.0.0 Flyway ベースライン説明 << Flyway Baseline >> Flyway マイグレーション・スクリプトの配置場所 classpath:db/migration spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/app?autoReconnect=true&useSSL=false username: guest password: guest flyway: enabled: true url: jdbc:mysql://localhost:3306/app?autoReconnect=true&useSSL=false user: guest password: guest baseline-on-migrate: true baseline-version: 0.0.0 baseline-description: << Flyway Baseline >> locations: classpath:db/migrationFlyway実行 (アプリケーション起動)
アプリケーションの起動時にFlywayを実行します。
SpringBoot 実行
以下のコマンドで SpringBoot を実行します。
$ ./gradlew bootRun
起動時ログから Flyway が正常実行されている事が確認できます。
[ main] o.f.c.internal.license.VersionPrinter : Flyway Community Edition 5.2.4 by Boxfuse [ main] o.f.c.internal.database.DatabaseFactory : Database: jdbc:mysql://localhost:3306/app (MySQL 5.7) [ main] o.f.core.internal.command.DbValidate : Successfully validated 2 migrations (execution time 00:00.019s) [ main] o.f.c.i.s.JdbcTableSchemaHistory : Creating Schema History table: `app`.`flyway_schema_history` [ main] o.f.core.internal.command.DbMigrate : Current version of schema `app`: << Empty Schema >> [ main] o.f.core.internal.command.DbMigrate : Migrating schema `app` to version 1.0.0 - Create-InitialTable [ main] o.f.core.internal.command.DbMigrate : Migrating schema `app` to version 1.1.0 - Insert-InitialData [ main] o.f.core.internal.command.DbMigrate : Successfully applied 2 migrations to schema `app` (execution time 00:00.109s)MySQL 確認
MySQLにアクセスしFlywayによる変更を確認します。
mysql> show tables; +-----------------------+ | Tables_in_app | +-----------------------+ | flyway_schema_history | | message | +-----------------------+mysql> select id,title,message from message; +--------------------------------------+-------+--------------------+ | id | title | message | +--------------------------------------+-------+--------------------+ | 12345678-e9d9-4d1e-ba79-01f8b8715ba9 | INIT | Inserted by FLYWAY | | 7b23257c-e9d9-4d1e-ba79-01f8b8715ba9 | INIT | Inserted by FLYWAY | +--------------------------------------+-------+--------------------+ 2 rows in set (0.00 sec)
message
テーブルと、Flyway の履歴管理テーブルflyway_schema_history
が作成されている事が確認でき、
データが追加されている事も確認できました。まとめ / 振り返り
組み込みモードでもサーバモードでも起動しているデータベースに対してアクセスする事ができれば、Flywayが使用できる事が確認できました。
データベースレイアウトに変更が発生しがちな開発中のタイミングでは非常に便利なツールだと思います。今回のソース
- 投稿日:2019-04-03T18:16:51+09:00
Kotlin の Suspending 関数を Mockito で spy する
mockito-kotlin を使って Suspending 関数を含むクラスのテストを書いていたところ、現状ではちょっとしたコツが必要なことが分かった。
以下の
DownloadService#downloadFile()
を呼び出す別のメソッドをテストするため、このインターネットと通信するdownloadFile()
メソッドをモックに置き換えようとした。
mock()
を使った以下のコードは正常に実行可能。class DownloadService { suspend fun downloadFile(url: String): File { // 省略 } // 実際には別のメソッドも定義されている } @Test fun testDownloadFile() { val file = createTempFile() val target = mock<DownloadService>() { onBlocking{ downloadFile(any()) } doReturn file } runBlocking { val downloadedFile = target.downloadFile("http://example.com") assertEquals(file, downloadedFile) } }実際には
DownloadService
クラスに含まれるdownloadFile()
メソッド以外はモックを使わずにそのまま実行されて欲しかったので、mock()
をspy()
に置き換え、このメソッドだけをモック実装する形に。@Test fun testDownloadFile() { val file = createTempFile() val target = spy<DownloadService>() { onBlocking{ downloadFile(any()) } doReturn file } runBlocking { val downloadedFile = target.downloadFile("http://example.com") assertEquals(file, downloadedFile) } }ところが、このテストコードを実行すると
NullPointerException
がスローされてしまう(4 行目で実行される Stub 処理中に)。
mock()
はいいけどspy()
だとダメ。
しかしながら、このようにspy()
を使ってもモック対象(今回の例で言うとdownloadFile()
メソッド)が引数を取らない場合は、NPE がスローされることなくテストを実行できることが分かった。試行錯誤した結果、
spy()
にラムダ式でモック処理を渡すのではなく、次のようにdoReturn().whenever().xxxx()
のコードでモック処理を書けば正常にテストが実行できるようになった。@Test fun testDownloadFile() = runBlocking { val file = createTempFile() val target = spy<DownloadService>() doReturn(file).whenever(target).downloadFile(any()) val downloadedFile = target.downloadFile("http://example.com") assertEquals(file, downloadedFile) }