FlywayによるDBマイグレーション

みなさん、こんにちは。サイオステクノロジー武井です。DBマイグレーションツールflywayを触る機会があったので、利用用途や利用方法をブログに残したいと思います。

Flywayとは?

DBマイグレーションツールです。スキーマの作成や変更などの処理をリリースするアプリの中に埋め込むことができます。

Flywayが必要になる理由

ID・姓・名の3つのフィールドを持つusersという名前のテーブルがあるとします。DDLは以下のとおりです。

CREATE TABLE users (
   id INT AUTO_INCREMENT PRIMARY KEY,
   last_name VARCHAR(100) NOT NULL,
   first_name VARCHAR(100) NOT NULL
);

まず、このテーブルを管理するアプリをリリースできたとします。

そして、次に改修要件として、メールアドレスを格納するフィールドmailを追加する必要がでてきて、Aさんはその開発担当者にアサインされました。

Aさんは、テーブルを変更するために、Aさん専用の開発環境用データベースに以下のDDLを適用し、開発を行い、無事完了させました。

ALTER TABLE users ADD mail VARCHAR(255);

しかし、Aさんは上記のスキーマ変更のことを誰にも共有せず、改修したソースだけをリポジトリにPushしました。

Bさんは、AさんがリポジトリにPushしたコードをPullしましたが、当然動きません。Bさん専用の開発環境用データベースには上記のDDLが適用されていない、つまりmailフィールドがないからです。

このような事態を防ぐには、「アプリケーションのバージョンとデータベース構成のバージョンを一致させること」が必須となるのですが、それを実現するのがflywayです。

やってみる

先程挙げた以下のユースケースをflywayを使って実際にやってみたいと思います。

  1. usersテーブルを作成し、そのテーブルを管理するアプリを開発する。
  2. usersテーブルにmailフィールドを追加し、mailフィールドを取得できるようアプリを改修する。

usersテーブルを作成し、そのテーブルを管理するアプリを開発する

バージョン管理にgradle、データベースにMySQLを使うとして、gradleに以下の設定を追加します。

dependencies {
  runtimeOnly 'org.flywaydb:flyway-mysql' // 追加
  runtimeOnly 'com.mysql:mysql-connector-j' // 追加
  ...
}

 

次にapplication.ymlに以下の内容を追記します。

spring:
  datasource:
    url: jdbc:mysql://127.0.0.1:3306/db # JDBC接続文字列
    username: user // データベース接続ユーザー
    password: password // データベース接続パスワード
    driver-class-name: com.mysql.cj.jdbc.Driver # ドライバークラス名
flyway:
  enabled: true # flyawyを有効にする

 

データベースに適用させたいSQLを定義します。V1__create_table1.sqlという名前のファイルを作成し、以下の内容を追記します。

CREATE TABLE users (
   id INT AUTO_INCREMENT PRIMARY KEY,
   last_name VARCHAR(100) NOT NULL,
   first_name VARCHAR(100) NOT NULL
);

このファイル名は以下の規則によって命名されています。

V[バージョン番号]__[説明].sql

[バージョン番号]はこの番号が若い順から適用されていきます。仮にV1__create_table1.sqlとV2__alter_table1.sqlというファイル名があった場合、V1__create_table1.sql→V2__alter_table1.sqlの順にSQLが実行されます。 [説明]はこのSQL文がどういったものなのかを表す説明になります。日本語OKです。

先程作成したV1__create_table1.sqlを以下のディレクトリに配置します。デフォルトではクラスパスの通っているディレクトリ配下にdb/migrationというディレクトリを作成し、そこにマイグレーションしたいSQLファイルを配置します。

main
└── resources
    └── db
        └── migration
            └── V1__create_table1.sql

usersテーブルを操作するアプリを開発します。

そして以下のコマンドを実行します。

$ ./gradlew bootRun

すると以下のテーブルが作成されているはずです。

  • flyway_schema_history
  • users

flyway_schema_historyはマイグレーション情報を管理するテーブルです。どのマイグレーションがすでに適用されたか、またそれらのマイグレーションがいつ適用されたかを追跡するために使用します。

そしてこれらのファイルをリポジトリにPushします。

usersテーブルにmailフィールドを追加し、mailフィールドを取得できるようアプリを改修する

usersテーブルにmailフィールドを追加するためのSQLを定義します。V2__alter_table1.sqlという名前のファイルを作成し、以下の内容を追記します。

ALTER TABLE users ADD mail VARCHAR(255)

 

先程作成したV2__alter_table1.sqlを以下のディレクトリに配置します。

main
└── resources
    └── db
        └── migration
            ├── V1__create_table1.sql
            └── V2__alter_table1.sql

 

usersテーブルのmailフィールドを取得するアプリを開発します。

そして以下のコマンドを実行します。

$ ./gradlew bootRun

するとusersテーブルにmailフィールドが追加されているはずです。

そしてこれらのファイルをリポジトリにPushします。

つまりアプリを実行すると同時に、事前に定義したスキーマ情報(db/migration配下に作成されたSQLファイル)に従い、flywayがテーブルを作成してくれます。

アプリのリリースとデータベースの構成が同時に行われるので、先に説明したようにアプリの改修だけ反映されて、それに伴うスキーマの構成が未反映という状態はなくなります。

便利ですね。

既にテーブルが存在する場合

既にテーブルが存在している場合のマイグレーションの方法を説明します。

つまり先の例で言えば、既にusersテーブルは作成されているけれど、mailフィールドが追加されていない状態からflywayを利用したい場合です。

このような場合はベースラインバージョンというものを指定します。つまり、どのSQLから適用させたいかを指定するのです。今回の場合は、V1__create_table1.sql(バージョン1)はスキップして、V2__alter_table1.sql(バージョン2)から適用させたいですよね。

main
└── resources
    └── db
        └── migration
            ├── V1__create_table1.sql ← これはスキップしたい
            └── V2__alter_table1.sql ← ここから適用させたい

 

そのためには、application.ymlにbaseline-versionとbaseline-on-migrateを追記します。

spring:
  datasource:
    url: jdbc:mysql://127.0.0.1:3306/db # JDBC接続文字列
    username: user // データベース接続ユーザー
    password: password // データベース接続パスワード
    driver-class-name: com.mysql.cj.jdbc.Driver # ドライバークラス名
flyway:
  baseline-version: 1 # バージョン1をスキップする場合、ベースラインバージョンは1となる
  enabled: true # flyawyを有効にする
  baseline-on-migrate: true # 既にテーブルが存在するときにマイグレーションする場合はtrueとする

 

そして以下のコマンドを実行します。

$ ./gradlew bootRun

 

flyway_schema_historyテーブルが作成され、usersテーブルにmailフィールドが追加されているはずです。

まとめ

flywayいい感じですよね。つかってみようかなぁ。

ご覧いただきありがとうございます! この投稿はお役に立ちましたか?

役に立った 役に立たなかった

0人がこの投稿は役に立ったと言っています。

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です