FlywayをJavaプログラムから使ってみる、その2
FlywayをJavaプログラムから使ってみる、その1の続きである。
Javaプログラムによるマイグレーション
SQLによるマイグレーションは確認したが、Javaプログラムからも行ってみる。これの何がうれしいかというと、SQLでは記述できないことが記述できることだ。
例えば、いったんはBLOBにJSONとして様々な値を格納したが、遅いのでやっぱりそのJSONの中身をテーブルフィールドにしたい等という要求がある。あるいはその逆もだ。
これは簡単だった。先のFlywayの命名規則に沿っていれば、SQLファイルだろうがJavaクラスだろうが区別なく扱ってくれる。先の例のドットを下線に変更し、二番目をJavaで記述する。
として、後者は以下とする。
package db.migration;
import java.sql.*;
import org.flywaydb.core.api.migration.*;
public class V2019_07_13_02__changing_table extends BaseJavaMigration {
public void migrate(Context context) throws Exception {
try (Statement stmt = context.getConnection().createStatement()) {
stmt.execute("alter table car add remarks varchar(80)");
stmt.execute("INSERT INTO car (id, name, color) VALUES (2, 'Isuzu', 'blue')");
}
}
}
DBを削除してやり直してみると、ちゃんとこの通りに実行されている。
マイグレーションに失敗したらどうなるのか?
次のような失敗するSQLを書いてみる。
V2019_07_13_03__failing.sql
alter table car drop remarks;
alter table car drop nothing;
当然エラーになる。
Exception in thread "main" org.flywaydb.core.internal.command.DbMigrate$FlywayMigrateException:
Migration V2019_07_13_03__failing.sql failed
--------------------------------------------
SQL State : 42000
Error Code : 1091
Message : (conn=12) Can't DROP COLUMN `nothing`; check that it exists
Location : db/migration/V2019_07_13_03__failing.sql (C:\devel\workspace\flyway\bin\default\db\migration\V2019_07_13_03__failing.sql)
Line : 2
Statement : alter table car drop nothing
at org.flywaydb.core.internal.command.DbMigrate.doMigrateGroup(DbMigrate.java:370)
at org.flywaydb.core.internal.command.DbMigrate.access$200(DbMigrate.java:54)
テーブルの方を見てみると、remarks列は削除されている。もう一度実行してみると、今度は以下のエラー。
Exception in thread "main" org.flywaydb.core.api.FlywayException: Validate failed: Detected failed migration to version 2019.07.13.03 (failing)
at org.flywaydb.core.Flyway.doValidate(Flyway.java:1482)
at org.flywaydb.core.Flyway.access$100(Flyway.java:85)
at org.flywaydb.core.Flyway$1.execute(Flyway.java:1364)
at org.flywaydb.core.Flyway$1.execute(Flyway.java:1356)
at org.flywaydb.core.Flyway.execute(Flyway.java:1711)
at org.flywaydb.core.Flyway.migrate(Flyway.java:1356)
at flywaytest.Main.main(Main.java:11)
どうやって復旧するのだろうか?
とりあえず、適用履歴を見てみると、たしかに3つ目は失敗している。
MariaDB [mydb]> select version, checksum, type, success from flyway_schema_history;
---------------+------------+------+---------+
version | checksum | type | success |
---------------+------------+------+---------+
2019.07.13.01 | -223036259 | SQL | 1 |
2019.07.13.02 | NULL | JDBC | 1 |
2019.07.13.03 | -710948869 | SQL | 0 |
---------------+------------+------+---------+
rows in set (0.001 sec)
あちこちに記述があるのだが、FlywayはSQLファイルのチェックサムをとっているので、単純にSQLファイルを修正して再実行してはいけないという。しかし、このテーブルを見てみれば、最後のエントリを削除し、削除されたフィールドを復旧し、SQLを修正すればいけそうな気がする。
delete from flyway_schema_history where version='2019.07.13.03';
alter table car add remarks varchar(80);
と修正し、先のSQLを以下に修正してみる。
alter table car drop remarks;
alter table car drop color;
これでうまくいった。
repairを使う
上記のようなことを行わなくとも、repair()を呼び出せば失敗した履歴を削除してくれるらしい。そしてrepair()は問題がなくとも呼び出して構わないらしい。
そこでいったんDBを削除し、再作成し、Javaコードを以下にして、再度実行してみる。
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", "root").load();
flyway.repair();
flyway.migrate();
}
}
当然、03でエラーが表示される。既にremarks列は削除されているので、03を以下に修正してみる。
alter table car drop color;
もう一度実行すると。意図通りの結果になる。
※もちろん、これはやってはいけない。DBの状態を02時点までいったん戻してから、03を元の意図通りに修正する必要がある。
複数開発者によるマイグレーション
FlywayはDDL文を順番に実行していくことを前提としているが、複数開発者のいる場合はそうもいかないこともある。この場合はどうなるのか?以下に例がある。
最後の教訓にあるが「conflictして修正した方が安全」だろう。つまり、これまで例として使ってきた日付方式ではなく、連番方式の方が安全だ。つまり、
V1__foo.sql
V2__bar.sql
V3__foobar.sql
などと必ず連番で構成するということだ。ちなみに、AさんがV3_foobar.sqlを作成し、BさんがV3_sample.sqlを作成した場合、Flywayはそれらを同じものとみなすので実行時にエラーを出してくれる。必ずしも、git上あるいはsvn上でconflictする必要は無いということだ。