概要
巷にはDBマイグレーションツールなるものがあります。この、マイグレーションというのは移行を意味しますが、いわゆる旧環境から新環境への移行といったデータ移行を意味するものではないのです。DB環境(DBスキーマ+データ)を移行して、同一状態のDB環境を手軽に構築できるようするツールなのです。
ここではFlywayというDBマイグレーションツールを試してみます。DB環境をバージョン管理して、DB移行を簡単にしてくれるツールです。
開発時、複数の開発者がそれぞれのDBを使うことがあります。それらのDB環境を一致させることが簡単になるので、古いDB環境を使って間違った実装・テストをするといった事態を未然に防止できるようになります。
ソースコードのバージョン管理が開発を支えるように、DB環境のバージョン管理も開発を支えてくれるはず。今後、システム開発の現場でDBマイグレーションツールの利用を検討する機会になれば嬉しい、という思いを込めて、メモしておきます。(少なくとも自分自身の備忘録にはなるはず)
Flyway + H2 + Gradle で試す
手軽にFlywayを試してみて、そのパワーを感じてみます。DBにH2を使い、gradle経由でFlywayを使います。通常開発でFlyway単発で使うことは少ないでしょうから。
環境
- OS-X (ここでは。WindowsでもCentOSでもいける。)
- Java(最新バージョン入れといたら間違いない。)
- H2(もちろんMySQLとかでもよい)
準備
このメモで完結できるよう、インストールについてもここに書いておきます。
インストールと言ってもアーカイブを展開するだけ。
- H2のインストール
- Gradleのインストール
1.H2のインストール
H2はJavaでできたRDBエンジン。HPより最新版をダウンロードする。2015/12/2時点の最新バージョンは1.4.190。
適当なフォルダに展開して、h2/bin/h2.sh
を実行すると、ブラウザが起動し、H2 Console が開く。
なおOS-X環境だと、上記ファイルを実行すると正しく動かない。
$ ./h2.sh zsh: ./h2.sh: bad interpreter: /bin/sh^M: no such file or directory
改行コードがDOS(CR+LF)になっているから。
UNIX(LF)にしてやるとうまくいく。$ cp h2.sh h2.sh.org $ tr -d ¥¥r < h2.sh.org > h2.sh $ chmod +x h2.sh
2.Gradleのインストール
gradleのHPからアーカイブをダウンロード。Binary only distributionでよい。2012/12/1現在の最新バージョンは2.9。
それを適当なフォルダに展開する。
個人的にはsdkmanを使ったやり方で入れてる。
OS-Xであればbrew
でインストールするのが簡単かも。
ここまでで準備は完了。
Flywayを扱えるプロジェクトを作る
Gradleでプロジェクトを作る
まず、gradle init
を実行してプロジェクトを作る。
$ gradle init --type java-library
Starting a new Gradle Daemon for this build (subsequent builds will be faster).
:wrapper
:init
BUILD SUCCESSFUL
Total time: 7.385 secs
こんな感じでフォルダが生成される。
$ du
176 ./.gradle/2.9/taskArtifacts
176 ./.gradle/2.9
176 ./.gradle
120 ./gradle/wrapper
120 ./gradle
8 ./src/main/java
8 ./src/main
8 ./src/test/java
8 ./src/test
16 ./src
352 .
build.gradleファイルにflyway関連ライブラリを組み込む
カレントディレクトリに、__build.gradle__というファイルができる。これを修正してflywayライブラリを組み込む。
修正後の__build.gradle__は以下のような感じ。
mavenCnetral()でなくjcenter()でもいいはず。
生成されたコメントは消してます。
buildscript {
repositories {
mavenCentral()
}
dependencies {
classpath 'com.h2database:h2:1.4.190'
classpath 'org.flywaydb:flyway-gradle-plugin:3.2.1'
}
}
apply plugin: 'java'
apply plugin: 'org.flywaydb.flyway'
repositories {
mavenCentral()
}
dependencies {
compile 'org.slf4j:slf4j-api:1.7.13' // init時のもの
testCompile 'junit:junit:4.12' // init時のもの
}
flyway {
user = 'sa'
//url = 'jdbc:h2:./dummy' // Embedded
url = 'jdbc:h2:tcp://localhost/~/dummy' // c/s
}
うまく設定できているかを確認してみる。gradle tasks
を実行して、flywayタスクがあればとりあえずOK。
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.
flywayInit - Baselines an existing database, excluding all migrations up to and including baselineVersion.
flywayMigrate - Migrates the schema to the latest version.
flywayRepair - Repairs the Flyway metadata table.
flywayValidate - Validates the applied migrations against the ones available on the classpath.
Flywayを試す
マイグレーション用のSQLファイルを追加する
まずmigrationフォルダを作る。
$ mkdir -p src/main/resources/db/migration
次に初期DBを作成するSQLスクリプトを作る。先ほどのmigrationフォルダに、V1__Initial_DB.sqlというファイルを作成する。中身はこんな感じにする。
CREATE TABLE USER (
ID LONG NOT NULL IDENTITY,
USER_ID VARCHAR(10) NOT NULL,
PASSWORD VARCHAR(20) NOT NULL,
NAME VARCHAR(100) NOT NULL
);
マイグレーションファイルは命名規則が決まっている。
V1__説明.sql というフォーマット。
最初にプレフィックスが__V__ 。これがデフォルト。変更できる。
次にバージョン番号。数値。1.1や1_2_3などとかける。
次が区切り文字。__ これは固定。
次が説明文。日本語OK。
最後にサフィックス。デフォルトは__.sql__。変更できる。
マイグレーション状況を確認する
gradle flywayInfo
と入力し、DBの状態を確認する。
先ほど作成したマイグレーションファイルの情報が表示される。
$ gradle flywayInfo
:compileJava UP-TO-DATE
:processResources UP-TO-DATE
:classes UP-TO-DATE
:compileTestJava UP-TO-DATE
:processTestResources UP-TO-DATE
:testClasses UP-TO-DATE
:flywayInfo
+---------+-------------+---------------------+---------+
| Version | Description | Installed on | State |
+---------+-------------+---------------------+---------+
| 1 | Initial DB | | Pending |
+---------+-------------+---------------------+---------+
BUILD SUCCESSFUL
Total time: 0.516 secs
H2 Consoleを確認。特にDBは変化なし。
マイグレーションする
gradle flywayMigrate
と入力しマイグレーショする。
$ gradle flywayMigrate
:compileJava UP-TO-DATE
:processResources UP-TO-DATE
:classes UP-TO-DATE
:compileTestJava UP-TO-DATE
:processTestResources UP-TO-DATE
:testClasses UP-TO-DATE
:flywayMigrate
BUILD SUCCESSFUL
Total time: 0.531 secs
gradle flywayInfo
で確認。
$ gradle flywayInfo
:compileJava UP-TO-DATE
:processResources UP-TO-DATE
:classes UP-TO-DATE
:compileTestJava UP-TO-DATE
:processTestResources UP-TO-DATE
:testClasses UP-TO-DATE
:flywayInfo
+---------+-------------+---------------------+---------+
| Version | Description | Installed on | State |
+---------+-------------+---------------------+---------+
| 1 | Initial DB | 2015-12-01 01:39:15 | Success |
+---------+-------------+---------------------+---------+
BUILD SUCCESSFUL
Total time: 0.523 secs
StateがPengingからSuceessになる。
H2 ConsocleでDBを確認する。USERテーブルとschema_versionテーブルができている。
さらにマイグレーションする
次のバージョン、V1.1__User_Data.sql を追加してみる。
INSERT INTO USER (USER_ID, PASSWORD, NAME) VALUES ('TEST01', 'TEST', 'テストユーザー01');
INSERT INTO USER (USER_ID, PASSWORD, NAME) VALUES ('TEST02', 'TEST', 'テストユーザー02');
INSERT INTO USER (USER_ID, PASSWORD, NAME) VALUES ('TEST03', 'TEST', 'テストユーザー03');
INSERT INTO USER (USER_ID, PASSWORD, NAME) VALUES ('TEST04', 'TEST', 'テストユーザー04');
INSERT INTO USER (USER_ID, PASSWORD, NAME) VALUES ('TEST05', 'TEST', 'テストユーザー05');
gradle flywayInfo
で確認。
$ gradle flywayInfo
:compileJava UP-TO-DATE
:processResources
:classes
:compileTestJava
:processTestResources UP-TO-DATE
:testClasses
:flywayInfo
+---------+-------------+---------------------+---------+
| Version | Description | Installed on | State |
+---------+-------------+---------------------+---------+
| 1 | Initial DB | 2015-12-01 01:39:15 | Success |
| 1.1 | User Data | | Pending |
+---------+-------------+---------------------+---------+
BUILD SUCCESSFUL
Total time: 0.537 secs
Version 1.1 がPenging状態。
gradle flywayMigrate
して、gradle flywayInfo
する。
$ gradle flywayMigrate
:compileJava UP-TO-DATE
:processResources UP-TO-DATE
:classes UP-TO-DATE
:compileTestJava UP-TO-DATE
:processTestResources UP-TO-DATE
:testClasses UP-TO-DATE
:flywayMigrate
BUILD SUCCESSFUL
Total time: 0.515 secs
$ gradle flywayInfo
:compileJava UP-TO-DATE
:processResources UP-TO-DATE
:classes UP-TO-DATE
:compileTestJava UP-TO-DATE
:processTestResources UP-TO-DATE
:testClasses UP-TO-DATE
:flywayInfo
+---------+-------------+---------------------+---------+
| Version | Description | Installed on | State |
+---------+-------------+---------------------+---------+
| 1 | Initial DB | 2015-12-01 02:06:10 | Success |
| 1.1 | User Data | 2015-12-01 02:07:04 | Success |
+---------+-------------+---------------------+---------+
BUILD SUCCESSFUL
Total time: 0.513 secs
こんな感じでDB環境のバージョン管理をしていく。
その他
マイグレーション失敗時の対処方法
マイグレーションファイルにミスがあると当然のことながらマイグレーションに失敗する。
たとえば、こんなマイグレーションファイルを用意する。
ALTER TABLE USER ADD COLUMN UPDATE_DATE DATA; // 本当はDATE
gralde flywayMigrate
としてみる。すると、、、
$ gradle flywayMigrate
:compileJava UP-TO-DATE
:processResources UP-TO-DATE
:classes UP-TO-DATE
:compileTestJava UP-TO-DATE
:processTestResources UP-TO-DATE
:testClasses UP-TO-DATE
:flywayMigrate
Migration of schema "PUBLIC" to version 1.2 failed! Please restore backups and roll back database and code!
:flywayMigrate FAILED
FAILURE: Build failed with an exception.
* What went wrong:
Execution failed for task ':flywayMigrate'.
> Error occurred while executing flywayMigrate
Migration V1.2__Modify_User.sql failed
--------------------------------------
SQL State : HY004
Error Code : 50004
Message : 不明なデータ型: "DATA"
Unknown data type: "DATA"; SQL statement:
ALTER TABLE USER ADD COLUMN UPDATE_DATE DATA [50004-190]
Location : db/migration/V1.2__Modify_User.sql (/Users/hagi/tmp/flyway/build/resources/main/db/migration/V1.2__Modify_User.sql)
Line : 1
Statement : ALTER TABLE USER ADD COLUMN UPDATE_DATE DATA
不明なデータ型: "DATA"
Unknown data type: "DATA"; SQL statement:
ALTER TABLE USER ADD COLUMN UPDATE_DATE DATA [50004-190]
* Try:
Run with --stacktrace option to get the stack trace. Run with --info or --debug option to get more log output.
BUILD FAILED
Total time: 0.555 secs
gradle flywayInfo
とすると、こんな感じになる。
$ gradle flywayInfo x [火 1 22:49:07]
:compileJava UP-TO-DATE
:processResources UP-TO-DATE
:classes UP-TO-DATE
:compileTestJava UP-TO-DATE
:processTestResources UP-TO-DATE
:testClasses UP-TO-DATE
:flywayInfo
+---------+-------------+---------------------+---------+
| Version | Description | Installed on | State |
+---------+-------------+---------------------+---------+
| 1 | Initial DB | 2015-12-01 22:46:53 | Success |
| 1.1 | User Data | 2015-12-01 22:46:53 | Success |
| 1.2 | Modify User | 2015-12-01 22:49:07 | Failed |
+---------+-------------+---------------------+---------+
BUILD SUCCESSFUL
Total time: 0.552 secs
Version1.2のマイグレーションのStateがFailedとなる。
こうなったら、gradle flywayRepair
としてPendingに戻し、マイグレーションファイルを修正し、再度gradle flywayMigrate
してやる。
マイグレーションファイルのバリデーションについて
マイグレーション済みのV1.1__User_Data.sqlを変更して、gradle flywayValidate
をすると、こんな感じでやっちまったなぁ、となる。
DBに適用済みのバージョンとローカルにあるマイグレーションファイルが異なることを教えてくれる。
$ gradle flywayValidate
:compileJava UP-TO-DATE
:processResources
:classes
:compileTestJava
:processTestResources UP-TO-DATE
:testClasses
:flywayValidate FAILED
FAILURE: Build failed with an exception.
* What went wrong:
Execution failed for task ':flywayValidate'.
> Error occurred while executing flywayValidate
Validate failed. Migration Checksum mismatch for migration 1.1
-> Applied to database : -1549820110
-> Resolved locally : -702322096
* Try:
Run with --stacktrace option to get the stack trace. Run with --info or --debug option to get more log output.
BUILD FAILED
Total time: 0.532 secs
この場合は、V1.1__User_Data.sqlを元に戻すか、再度、V1からマイグレーションすればいい。
最後に
Flywayをうまく使えばシステム開発で必須のDB環境の管理が楽になる。なまじSQLのスキルがあるとテーブルを直接いじったりして、SQLスクリプトとして残らないことがある。たとえば、ALTER TABE
を直接実施して環境を調整してしまうとか。これを良しとしていると、DB環境の再現性があやしくなる。
最近やってたあのプロジェクトでそんな問題に遭遇したなぁ。
今やってるあのプロジェクトに導入しておきたいなぁ。