FlywayをJavaプログラムから使ってみる、その1

2019年7月14日

Flywayとは?

Flywayとは、データベースの「マイグレーションツール」と説明しているところが多いのだが、マイグレーション(移行)というよりも、自動スキーマアップデータと呼んだ方が適切だ。

Flywayのやることは要するにこういうことらしい。

  • データベースのスキーマを変更したくなった(フィールド追加とか、テーブル追加とか)。
  • そのSQLを書いてやる。
  • これをFlywayに食わせると、それに即して自動でスキーマ変更をやってくれる。
  • これらのSQLをバージョン管理することにより、古いスキーマのDBを自動更新してくれる。

といったものだ。要するに、

  • 開発中には、どんどんDBスキーマが変更される。Flywayが開発者全員のDBを自動更新してくれる。
  • リリース後に、リリース先が複数の場合にも、いちいち手で対応する必要なし。Flywayが自動更新してくれる。

というものだ。Flywayなどを使わずとも、こういった管理をするのは当然だろう。手作業でやるのは無茶というものだ。

例えば、Flywayを使わない場合であっても、以下のようにするだろう。

  • スキーマバージョンを保持する何らかのテーブルを作り、そこに1と書き込む
  • バージョン2に移行する場合のSQLを記述し、最新バージョンとして2を書き込む
  • プログラム起動時にDBが現在の最新バージョンでなければ、そのSQL(DDL)を実行する
  • これを繰り返す

といった具合だ。このようなことをFlywayが自動で面倒みてくれるというわけだ。

Javaプログラムから使用する

検索してみると、ほとんどが「手作業で」Flywayを使うか、あるいはGradle等から起動する説明ばかりでJavaプログラムで自動化する方法が見られない。

スキーマ変更を行うのが開発者のみであればいざしらず、客先(それも複数)で行ってもらうことは想定できない。「バージョンアップしたプログラムを客先に入れ、客が起動したら、自動でスキーマ変更」されねばならないのだ。

そのためには、FlywayをJavaプログラムに組み込み、プログラム起動時に自動で行わねばならない。以下これを見ていく。

これについては、Flyway APIに説明がある。

環境と最低限のプログラム

DBとしては、Windows7-64のmariadb-10.4.6とした。これをインストールする。

次にgradleに次の依存を記述してライブラリをロードする。

dependencies {
  compile group: 'org.flywaydb', name: 'flyway-core', version: '5.2.4'
  compile group: 'org.mariadb.jdbc', name: 'mariadb-java-client', version: '2.4.2'  
}

※後述するが、2.4.2ドライバでは接続できなかった。

Javaプログラムとしては以下だ。

package flywaytest;
import org.flywaydb.core.*;
public class Main {
  public static void main(String[]args) throws Exception {
    Class.forName("org.mariadb.jdbc.Driver");
    String url = "jdbc:mariadb://localhost/mydb?useUnicode=true&characterEncoding=utf8";
    Flyway flyway = Flyway.configure().dataSource(url, "root", "パスワード").load();
    flyway.migrate();
  }
}

当然だが、mydbは存在せず、SQLを一行も書いてないので以下のエラーになる。

7月 13, 2019 7:27:32 午前 org.flywaydb.core.internal.license.VersionPrinter printVersionOnly
情報: Flyway Community Edition 5.2.4 by Boxfuse
Exception in thread "main" org.flywaydb.core.internal.exception.FlywaySqlException: 
Unable to obtain connection from database (jdbc:mariadb://localhost/mydb?useUnicode=true&characterEncoding=utf8) for user 'root': Unknown database 'mydb'
---------------------------------------------------------------------------------------------------------------------------------------------------------
SQL State  : 42000
Error Code : 1049
Message    : Unknown database 'mydb'

    at org.flywaydb.core.internal.jdbc.JdbcUtils.openConnection(JdbcUtils.java:60)
    at org.flywaydb.core.internal.database.DatabaseFactory.createDatabase(DatabaseFactory.java:72)
    at org.flywaydb.core.Flyway.execute(Flyway.java:1670)
    at org.flywaydb.core.Flyway.migrate(Flyway.java:1356)
    at flywaytest.Main.main(Main.java:11)

おっと、flywayはDBの作成はしてくれないらしい。以下を実行、

create database mydb default character set utf8;

もう一度実行する。

7月 13, 2019 7:50:54 午前 org.flywaydb.core.internal.license.VersionPrinter printVersionOnly
情報: Flyway Community Edition 5.2.4 by Boxfuse
7月 13, 2019 7:50:54 午前 org.flywaydb.core.internal.database.DatabaseFactory createDatabase
情報: Database: jdbc:mariadb://localhost/mydb (MariaDB 10.4)
Exception in thread "main" org.flywaydb.core.api.FlywayException: Unsupported Database: MariaDB 10.4
    at org.flywaydb.core.internal.jdbc.DatabaseType.fromDatabaseProductNameAndPostgreSQLVersion(DatabaseType.java:168)
    at org.flywaydb.core.internal.jdbc.DatabaseType.fromJdbcConnection(DatabaseType.java:117)

MariaDB 10.4はサポートしていないという。たしかに、現時点(2019/7/13)では10.3までになっている。

これは結構DB種類やバージョンに厳しいようだ。「10.4でもいいじゃないか」というわけにはいかないらしい。

MariaDB 10.3で実行

10.3に変更してみる。が、

情報: Database: jdbc:mariadb://localhost/mydb (MariaDB 10.3)
Exception in thread "main" org.flywaydb.core.api.FlywayException: Unsupported Database: MariaDB 10.3
    at org.flywaydb.core.internal.jdbc.DatabaseType.fromDatabaseProductNameAndPostgreSQLVersion(DatabaseType.java:168)
    at org.flywaydb.core.internal.jdbc.DatabaseType.fromJdbcConnection(DatabaseType.java:117)

うまくいかない。先のMariaDB Supported Versionsを見てみるとドライバが2.3.0になっているので、これに変更してみる。

dependencies {
  compile group: 'org.flywaydb', name: 'flyway-core', version: '5.2.4'
  compile group: 'org.mariadb.jdbc', name: 'mariadb-java-client', version: '2.3.0'  
}

今回は以下だ。

7月 13, 2019 8:37:31 午前 org.flywaydb.core.internal.license.VersionPrinter printVersionOnly
情報: Flyway Community Edition 5.2.4 by Boxfuse
7月 13, 2019 8:37:31 午前 org.flywaydb.core.internal.database.DatabaseFactory createDatabase
情報: Database: jdbc:mariadb://localhost/mydb (MySQL 10.3)
7月 13, 2019 8:37:31 午前 org.flywaydb.core.internal.command.DbValidate validate
情報: Successfully validated 0 migrations (execution time 00:00.010s)
7月 13, 2019 8:37:31 午前 org.flywaydb.core.internal.schemahistory.JdbcTableSchemaHistory create
情報: Creating Schema History table: `mydb`.`flyway_schema_history`
7月 13, 2019 8:37:31 午前 org.flywaydb.core.internal.command.DbMigrate migrateGroup
情報: Current version of schema `mydb`: << Empty Schema >>
7月 13, 2019 8:37:31 午前 org.flywaydb.core.internal.command.DbMigrate logSummary
情報: Schema `mydb` is up to date. No migration necessary.

何のスキーマも指定していないのだが、DBにはflyway_schema_historyというテーブルが作成されている。

※もしかしたら、mariadb-java-clientのバージョンが2.3.0であれば、10.4でもいけるのかも。

SQLベースマイグレーション

実はFlywayには2つのモードがあり、コマンドライン等でも使えるものはSQLベースマイグレーションというものらしい。これは、一定の命名規約のファイル名による複数のsqlテキストファイルをおいておき、それをFlywayに処理させるというものだ。

Javaプログラムから行う場合にはこの方法も使えるし、SQLファイルではなく、JavaメソッドとしてDDLを実行させるJavaベースマイグレーションというものもあるらしい。Migrationsに説明がある。

とりあえず、SQLベースマイグレーションを行ってみる。単純に、Javaソースにdb.migrationというパッケージを作成し(ここがデフォルト)、その中にマイグレーションSQLファイルをFlywayの命名規則にしたがって入れればよい。

V2019.07.13.01__creating_table.sql

という名称で以下を記述してみる。

CREATE TABLE car (
    id INT NOT NULL PRIMARY KEY,
    name VARCHAR(80) NOT NULL,
    color VARCHAR(80) NOT NULL
);

INSERT INTO car (id, name, color) VALUES (1, 'DeLorean', 'red');

実行すると、たしかにDBが変更されている。

MariaDB [mydb]> show tables;
+-----------------------+
| Tables_in_mydb        |
+-----------------------+
| car                   |
| flyway_schema_history |
+-----------------------+
2 rows in set (0.001 sec)

MariaDB [mydb]> select * from car;
+----+----------+-------+
| id | name     | color |
+----+----------+-------+
|  1 | DeLorean | red   |
+----+----------+-------+
1 row in set (0.001 sec)

次に、

V2019.07.13.02__changing_table

という名称で以下を書き込む。

alter table car add remarks varchar(80);
INSERT INTO car (id, name, color) VALUES (2, 'Isuzu', 'blue');

実行してみると完全にうまく行っている。いったんDBを削除し、再作成して実行してもうまく行く。

再度MariaDB 10.4.6でやってみる

いったん10.3.16を削除して、10.4.6を再インストールしてやってみると、こんどはうまくいく。どうなってるのか???

※一応「 MariaDB 10.4 is newer than this version of Flyway and support has not been tested」とは警告しているが。。。

7月 13, 2019 8:59:47 午前 org.flywaydb.core.internal.license.VersionPrinter printVersionOnly
情報: Flyway Community Edition 5.2.4 by Boxfuse
7月 13, 2019 8:59:47 午前 org.flywaydb.core.internal.database.DatabaseFactory createDatabase
情報: Database: jdbc:mariadb://localhost/mydb (MySQL 10.4)
7月 13, 2019 8:59:47 午前 org.flywaydb.core.internal.database.base.Database recommendFlywayUpgradeIfNecessary
警告: Flyway upgrade recommended: MariaDB 10.4 is newer than this version of Flyway and support has not been tested.
7月 13, 2019 8:59:47 午前 org.flywaydb.core.internal.command.DbValidate validate
情報: Successfully validated 2 migrations (execution time 00:00.023s)
7月 13, 2019 8:59:47 午前 org.flywaydb.core.internal.schemahistory.JdbcTableSchemaHistory create
情報: Creating Schema History table: `mydb`.`flyway_schema_history`
7月 13, 2019 8:59:47 午前 org.flywaydb.core.internal.command.DbMigrate migrateGroup
情報: Current version of schema `mydb`: << Empty Schema >>
7月 13, 2019 8:59:47 午前 org.flywaydb.core.internal.command.DbMigrate doMigrateGroup
情報: Migrating schema `mydb` to version 2019.07.13.01 - creating table
7月 13, 2019 8:59:47 午前 org.flywaydb.core.internal.command.DbMigrate doMigrateGroup
情報: Migrating schema `mydb` to version 2019.07.13.02 - changing table
7月 13, 2019 8:59:47 午前 org.flywaydb.core.internal.command.DbMigrate logSummary
情報: Successfully applied 2 migrations to schema `mydb` (execution time 00:00.091s)

まとめ

公式ページではMaria DB10.4はサポート対象になっていないが、実際にはうまく行っているようだ。これはMariaDBのバージョンというよりもMaria DBドライバのバージョンに起因するのかもしれない。つまり、

dependencies {
  compile group: 'org.flywaydb', name: 'flyway-core', version: '5.2.4'
  compile group: 'org.mariadb.jdbc', name: 'mariadb-java-client', version: '2.4.2'  
}

では10.3, 10.4の両方とも動作せず、

  compile group: 'org.flywaydb', name: 'flyway-core', version: '5.2.4'
  compile group: 'org.mariadb.jdbc', name: 'mariadb-java-client', version: '2.3.0'  

では、10.3, 10.4の両方とも動作する。

あとは、以下のJavaプログラムを記述し、

package flywaytest;
import org.flywaydb.core.*;
public class Main {
  public static void main(String[]args) throws Exception {
    Class.forName("org.mariadb.jdbc.Driver");
    String url = "jdbc:mariadb://localhost/mydb?useUnicode=true&characterEncoding=utf8";
    Flyway flyway = Flyway.configure().dataSource(url, "root", "パスワード").load();
    flyway.migrate();
  }
}

db.migrationパッケージ内にFlywayの命名規則にしたがったファイルにDDLを記述すればよい。