Help us understand the problem. What is going on with this article?

DBマイグレーションツール活用のすすめ〜Flyway〜

More than 3 years have passed since last update.

概要

巷には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とかでもよい)

準備

このメモで完結できるよう、インストールについてもここに書いておきます。
インストールと言ってもアーカイブを展開するだけ。

  1. H2のインストール
  2. 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

スクリーンショット 2015-12-01 1.27.54.png

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()でもいいはず。
生成されたコメントは消してます。

build.gradle
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というファイルを作成する。中身はこんな感じにする。

V1__Initail_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は変化なし。

スクリーンショット 2015-12-01 1.36.07.png

マイグレーションする

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テーブルができている。
スクリーンショット 2015-12-01 1.41.18.png

なお、schema_versionテーブルはこんな感じ。
スクリーンショット 2015-12-01 1.43.01.png

さらにマイグレーションする

次のバージョン、V1.1__User_Data.sql を追加してみる。

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を見てみる。
スクリーンショット 2015-12-01 2.09.21.png

こんな感じでDB環境のバージョン管理をしていく。

その他

マイグレーション失敗時の対処方法

マイグレーションファイルにミスがあると当然のことながらマイグレーションに失敗する。
たとえば、こんなマイグレーションファイルを用意する。

V1.2__Modify_User.sql
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環境の再現性があやしくなる。

最近やってたあのプロジェクトでそんな問題に遭遇したなぁ。
今やってるあのプロジェクトに導入しておきたいなぁ。

参考

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした