【Flyway 実践ガイド】Flywayの高度なカスタマイズ:プロジェクトが直面する課題を解決するための実践的テクニック集

はじめに

データベースマイグレーションのプロセスを、より安全に、より効率的に、そしてチームにとってより快適なものにしたい、と考えたことはありませんか?

Flywayの基本的な使い方をマスターした次のステップは、その高度なカスタマイズ機能を活用して、プロジェクトが直面する現実的な課題を解決していくことです。

本記事では、Flywayの応用的なテクニックを学ぶことで得られる、以下の具体的なメリットに焦点を当てて解説を進めます。

  • デプロイ作業の安全性向上:
    • コールバックや設定のカスタマイズを駆使し、本番環境へのマイグレーションに伴うリスクを低減します。
  • 複数環境の効率的な管理:
    • プレースホルダーやflyway.tomlを活用し、開発・ステージング・本番といった環境ごとの設定管理をシンプルかつ確実なものにします。
  • チーム開発の円滑化:
    • 複数人での開発時に発生しがちなマイグレーションの競合といった課題に対し、実践的な解決策を提示します。

この記事を読み終える頃には、Testcontainersによる信頼性の高いテストパイプラインの構築、ゼロダウンタイムデプロイといった高度な課題への対応策など、Flywayのポテンシャルを最大限に引き出すための知識と自信が身についているはずです。


対象読者

  • Flywayの基本的な使い方(migrate, clean, infoコマンドなど)を理解している。
  • SQLの基本的な知識があり、データベーススキーマの変更管理に関わった経験がある。
  • より複雑なプロジェクト要件やチーム開発の課題に対応するため、Flywayの標準機能から一歩進んだ応用的な使い方を学びたい開発者、データベース管理者。

目次

  1. Flyway設定の深掘り:プロジェクトに合わせた柔軟な制御
  2. プレースホルダー活用術:環境ごとの違いをスマートに吸収する
  3. コールバックによる処理の拡張:マイグレーションライフサイクルのフック
  4. より高度なマイグレーション戦略
  5. Testcontainersによる信頼性の高いマイグレーションテスト
  6. FAQ:実践シナリオにおける課題解決
  7. まとめ
  8. 参考資料

1. Flyway設定の深掘り:プロジェクトに合わせた柔軟な制御

Flywayは、非常に多くの設定オプションを提供しており、これらを活用することで、プロジェクトの要件に合わせた柔軟なデータベースマイグレーションを実現できます。


1.1 設定プロパティの全貌

Flywayの挙動は、設定ファイルを通じて細かく制御できます。

Flyway v10からは、従来の .conf 形式に代わり、より表現力が高く構造化された flyway.toml ファイルが推奨されています。

また、Spring Boot環境では application.propertiesapplication.ymlspring.flyway.*flyway.* といったプレフィックスで設定します。

注意: バージョンアップによりパラメータ名が変更されることがあります。例えば、コマンドラインの -configFiles オプションは、flyway.toml 内では [flyway].configFiles というキーに対応します。常に公式ドキュメントで最新のパラメータ名を確認する習慣をつけましょう。

主要なプロパティをいくつか見てみましょう。

プロパティ名 (flyway.toml等)説明
flyway.urlデータベース接続URL
flyway.userデータベースユーザー名
flyway.passwordデータベースパスワード
flyway.locationsマイグレーションスクリプトが配置されている場所(カンマ区切り)
flyway.schemasFlywayが管理するスキーマ名(カンマ区切り、複数指定可能)
flyway.tableスキーマ履歴テーブル名(デフォルトは flyway_schema_history
flyway.baselineOnMigrateマイグレーション実行時にベースラインを自動的に作成するかどうか
flyway.cleanDisabledclean コマンドを無効にするかどうか(本番環境での誤操作防止に重要)
flyway.placeholders.keyプレースホルダーのキーと値のペア
flyway.callbacks(Java API利用時)コールバッククラスの指定
flyway.environmentflyway.toml内で使用する環境を指定(例: development, production

これらのプロパティを適切に設定することで、開発、ステージング、本番といった異なる環境でFlywayの挙動をきめ細かく調整できます。

また、v10.2から追加された flyway testConnection コマンドを使えば、これらの設定が正しいかをマイグレーション実行前に素早く確認できます。


1.2 Java APIでの設定:より動的な制御

Spring Bootアプリケーションでは、Javaコードを通じてFlywayの設定をより動的にカスタマイズできます。

特に、複数のデータベースを扱う場合や、複雑な条件に基づいて設定を変更したい場合に有効な手段です。


1.2.1 FlywayConfigurationCustomizer を使った設定

FlywayConfigurationCustomizer インターフェースを実装することで、Spring BootがFlywayを初期化する際に、プログラムで設定を追加・変更できます。

import org.flywaydb.core.api.configuration.FluentConfiguration;
import org.springframework.boot.autoconfigure.flyway.FlywayConfigurationCustomizer;
import org.springframework.stereotype.Component;

@Component
public class MyFlywayConfigurationCustomizer implements FlywayConfigurationCustomizer {

    @Override
    public void customize(FluentConfiguration configuration) {
        // 環境変数に応じてスキーマを設定
        String activeProfile = System.getProperty("spring.profiles.active", "dev");
        if ("prod".equals(activeProfile)) {
            configuration.schemas("my_prod_schema");
        } else {
            configuration.schemas("my_dev_schema");
        }

        // プレースホルダーの追加
        configuration.placeholder("app.version", "1.0.0");

        // クリーン操作の無効化(本番環境での安全策)
        if ("prod".equals(activeProfile)) {
            configuration.cleanDisabled(true);
        }
    }
}

この例では、アクティブなSpringプロファイルに応じて異なるスキーマを設定したり、アプリケーションバージョンをプレースホルダーとして追加したり、本番環境での clean コマンドを無効にしたりしています。


1.2.2 FlywayMigrationStrategy でマイグレーション実行を制御

FlywayMigrationStrategy を使うと、Flywayのマイグレーションが実行されるタイミングや方法をさらに細かく制御できます。

import org.flywaydb.core.Flyway;
import org.springframework.boot.autoconfigure.flyway.FlywayMigrationStrategy;
import org.springframework.stereotype.Component;

@Component
public class CustomFlywayMigrationStrategy implements FlywayMigrationStrategy {

    @Override
    public void migrate(Flyway flyway) {
        // 特定の環境でのみマイグレーションを実行
        String activeProfile = System.getProperty("spring.profiles.active", "dev");
        if (!"test".equals(activeProfile)) {
            flyway.migrate();
        } else {
            System.out.println("テスト環境ではFlywayマイグレーションをスキップします。");
        }
    }
}

この戦略は、例えばテスト環境でデータベースを完全にクリーンアップしてからインメモリDBを構築するような場合に、Flywayのマイグレーションをスキップするために利用できます。


2. プレースホルダー活用術:環境ごとの違いをスマートに吸収する

データベースマイグレーションでは、環境による設定値の違いを管理する必要があります。例えば、開発環境と本番環境では、接続先のデータベース名や利用するユーザー名が異なります。

SQLスクリプト内にこれらの値を直接記述(ハードコーディング)すると、環境ごとにスクリプトを修正する必要が生まれ、設定ミスなどの原因となります。この課題は、Flywayのプレースホルダー (Placeholders) 機能で解決できます。


2.1 プレースホルダーによる動的な値の置換

プレースホルダーを使えば、SQLスクリプト内に ${変数名} という形式で変数を埋め込み、Flyway実行時に設定ファイルから値を動的に注入できます。これにより、単一のSQLスクリプトを、変更することなく全ての環境で再利用できます。


ステップ1: プレースホルダーを使ったSQLスクリプトを作成

環境に依存する部分をプレースホルダーに置き換えたSQLスクリプトを用意します。

db/migration/V1__Grant_permissions.sql:

-- プレースホルダーを使い、どの環境でも利用できるSQLスクリプト
GRANT SELECT, INSERT, UPDATE, DELETE ON `${db_name}`.* TO '${app_user}'@'%';
FLUSH PRIVILEGES;

ステップ2: 環境ごとに設定ファイルを用意

次に、環境ごとのプレースホルダーの値を定義した設定ファイルを用意します。

conf/flyway.dev.conf:

# 開発環境用の設定
flyway.url=jdbc:mysql://localhost:3306/myapp_dev
flyway.user=root
flyway.password=password

# プレースホルダーの値を定義
flyway.placeholders.db_name=myapp_dev
flyway.placeholders.app_user=dev_user

conf/flyway.prod.conf:

# 本番環境用の設定
flyway.url=jdbc:mysql://prod-db.example.com:3306/myapp_prod
flyway.user=flyway_admin
flyway.password=${DB_PASSWORD} # 環境変数から取得

# プレースホルダーの値を定義
flyway.placeholders.db_name=myapp_prod
flyway.placeholders.app_user=prod_user

(Spring Bootの場合は application-dev.propertiesapplication-prod.propertiesflyway.placeholders.db_name=... のように記述します)


ステップ3: 設定ファイルを指定してFlywayを実行

Flywayのコマンドラインから、-configFiles オプションで環境に合った設定ファイルを指定してマイグレーションを実行します。

開発環境で実行する場合:

flyway -configFiles=conf/flyway.dev.conf migrate

このコマンドを実行すると、V1__Grant_permissions.sql内のプレースホルダーが置換され、データベースでは以下のSQLが実行されます。

-- 開発環境で実際に実行されるSQL
GRANT SELECT, INSERT, UPDATE, DELETE ON `myapp_dev`.* TO 'dev_user'@'%';
FLUSH PRIVILEGES;

2.2 発展:flyway.tomlによる複数環境設定の一元管理

flyway.confを環境ごとに用意する方法も確実ですが、ファイルが増えると管理が煩雑になりがちです。

Flyway v9から導入されたTOML設定ファイル (flyway.toml) を使うと、この問題をより効率的に解決できます。

単一のファイル内に、開発・ステージング・本番といった複数の環境定義をまとめて記述できます。


flyway.toml の設定例

これまでの開発(development)と本番(production)の例を、一つのflyway.tomlで表現します。

flyway.toml:

# Flywayのデフォルト設定
[flyway]
locations = ["filesystem:db/migration"]

# 開発環境 (development) の設定
[environments.development]
url = "jdbc:mysql://localhost:3306/myapp_dev"
user = "root"
password = "password"
placeholders.db_name = "myapp_dev"
placeholders.app_user = "dev_user"

# 本番環境 (production) の設定
[environments.production]
url = "jdbc:mysql://prod-db.example.com:3306/myapp_prod"
user = "flyway_admin"
password = "${DB_PROD_PASSWORD}" # 環境変数から取得
placeholders.db_name = "myapp_prod"
placeholders.app_user = "prod_user"
cleanDisabled = true

環境を指定してFlywayを実行

flyway.toml利用時、コマンドラインから -environment オプションで対象環境を指定します。

開発環境で実行する場合:

# 'development' 環境の設定が自動的に使われる
flyway migrate -environment=development

本番環境で実行する場合:

flyway migrate -environment=production

この方法は、どの環境の設定が使われるか一目瞭然で、設定ファイルも一つにまとまるため、リポジトリの見通しが良くなります。

複数環境の管理には、flyway.tomlの利用が有力な選択肢です。


2.3 実践例:DockerとCI/CDにおける環境変数との連携

flyway.toml や設定ファイルで定義したプレースホルダーは、実行環境の環境変数によって上書きできます。

これは、特にDockerやCI/CDパイプラインにおいて、データベースのパスワードのような秘匿情報をコードリポジトリに含めずに注入するための非常に重要な機能です。

Flywayは、FLYWAY_PLACEHOLDERS_ というプレフィックスを持つ環境変数を自動的にプレースホルダーの値として解釈します。

Docker Composeでの連携例

docker-compose.yml 内で、Flywayを実行するサービスに対して環境変数を設定します。

# docker-compose.yml
services:
  flyway-migrator:
    image: flyway/flyway
    volumes:
      - ./sql:/flyway/sql
    # 環境変数でプレースホルダーの値を上書き
    environment:
      - FLYWAY_URL=jdbc:mysql://db:3306/myapp
      - FLYWAY_USER=root
      - FLYWAY_PASSWORD=${DB_ROOT_PASSWORD} # ホストの環境変数や.envファイルから取得
      - FLYWAY_PLACEHOLDERS_APP_USER=cicd_user
      - FLYWAY_PLACEHOLDERS_DB_NAME=myapp_cicd
    command: migrate

上記の例では、SQLスクリプト内の ${app_user}${db_name} は、docker-compose.ymlで定義された cicd_usermyapp_cicd にそれぞれ置換されます。

GitHub Actionsでの連携例

GitHub Actionsのワークフローでも同様に、env キーワードを使って環境変数を設定できます。

# .github/workflows/migration.yml
jobs:
  migrate:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: red-gate/setup-flyway@v1
        with:
          version: latest
      - name: Run migration
        env:
          FLYWAY_URL: ${{ secrets.DB_URL }}
          FLYWAY_USER: ${{ secrets.DB_USER }}
          FLYWAY_PASSWORD: ${{ secrets.DB_PASSWORD }}
          FLYWAY_PLACEHOLDERS_APP_USER: "ga_user" # GitHub Actions用のユーザー
        run: flyway migrate

このように、環境変数を活用することで、単一のSQLマイグレーションスクリプトの再利用性を最大限に高めつつ、セキュリティも確保できます。


3. コールバックによる処理の拡張:マイグレーションライフサイクルのフック

Flywayのコールバック機能を使うと、マイグレーションの特定のイベント発生前後にカスタムロジックを挿入できます。

これにより、マイグレーションプロセスをより柔軟に制御し、外部システムとの連携や追加の検証を行うことが可能になります。


3.1 コールバックイベント一覧:処理を差し込むタイミング

Flywayは、各コマンドの実行ライフサイクル中に、処理を差し込むための豊富なイベントを提供しています。

JavaでCallbackインターフェースを実装する方法と、特定の命名規則に従ったSQLスクリプトを配置する方法の2種類があります。

以下に、主要なFlywayコマンドと、それに関連するコールバックイベントの一覧を示します。


migrate コマンドのライフサイクルイベント

イベントSQLコールバック実行タイミングと主な用途
beforeMigratebeforeMigrate.sqlmigrate処理全体の開始直後。マイグレーション全体の前提条件チェックや、開始ログの記録などに使用。
beforeEachMigratebeforeEachMigrate.sql各マイグレーションスクリプトの実行直前。特定のスクリプト実行前のバックアップ取得や、個別ログの記録に使用。
afterEachMigrateafterEachMigrate.sql各マイグレーションスクリプトの実行直後。スクリプトが正常に実行されたことの確認や、個別処理のクリーンアップに使用。
afterMigrateafterMigrate.sqlmigrate処理全体の完了直後(成功時のみ)。マイグレーション完了通知、キャッシュのクリアなどに使用。
onMigrateErroronMigrateError.sqlmigrate処理中にエラーが発生した場合。エラー通知、ロールバック処理のトリガー、デバッグ情報の記録などに使用。

migrateコマンドのイベントフロー(シーケンス図)

migrateコマンドのイベントフロー

その他のコマンドのライフサイクルイベント

イベントSQLコールバック実行タイミングと主な用途
beforeCleanbeforeClean.sqlcleanコマンド実行前。スキーマ削除の最終確認や、リソースのバックアップに使用。
afterCleanafterClean.sqlcleanコマンド実行後。クリーンアップ完了の通知などに使用。
beforeRepairbeforeRepair.sqlrepairコマンド実行前。修復操作のロギングなどに使用。
afterRepairafterRepair.sqlrepairコマンド実行後。修復結果の検証や通知に使用。

3.2 Java/SQLコールバックの実装例

Javaコールバックの例

Javaコールバックは Callback インターフェースを実装します。

import org.flywaydb.core.api.callback.Callback;
import org.flywaydb.core.api.callback.Context;
import org.flywaydb.core.api.callback.Event;

public class NotificationCallback implements Callback {

    @Override
    public boolean supports(Event event, Context context) {
        return event == Event.beforeMigrate; // migrateイベントの前にのみ実行
    }

    @Override
    public boolean canHandleInTransaction(Event event, Context context) {
        return true; // トランザクション内で実行可能
    }

    @Override
    public void handle(Event event, Context context) {
        System.out.println("Flywayマイグレーション開始を外部サービスに通知します...");
        // ここに外部サービス呼び出しなどのロジックを記述
    }

    @Override
    public String getCallbackName() {
        return "NotificationCallback";
    }
}

SQLコールバックの例

SQLコールバックは、特定の命名規則に従ったSQLファイルとして配置します。

-- beforeMigrate.sql
-- マイグレーション開始前に監査ログを記録
INSERT INTO audit_log (event_type, description, timestamp)
VALUES ('FLYWAY_MIGRATION', 'Migration started', NOW());

3.3 実践例:GitHub ActionsでのSQLコールバックによる通知

SQLコールバックは、CI/CDパイプラインに組み込むことで真価を発揮します。

ここでは、GitHub ActionsのワークフローでFlywayのマイグレーションを実行し、その前後でSQLコールバックをフックしてログを出力する例を紹介します。

ステップ1: コールバック用のSQLスクリプトを用意

リポジトリに callbacks ディレクトリを作成し、以下のSQLファイルを配置します。

callbacks/beforeMigrate.sql:

-- PostgreSQLの場合
DO $$
BEGIN
    RAISE NOTICE 'Flyway migration is about to start.';
END $$;

callbacks/afterMigrate.sql:

-- PostgreSQLの場合
DO $$
BEGIN
    RAISE NOTICE 'Flyway migration finished successfully.';
END $$;

ステップ2: GitHub Actions ワークフローを編集

ワークフローファイル内で、flyway migrate コマンドの -locations パラメータに、通常のマイグレーションディレクトリ(例:db/migration)とコールバック用ディレクトリ(callbacks)の両方を指定します。

.github/workflows/flyway-migrate.yml:

name: Flyway CI

on:
  push:
    branches: [ main ]

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v4

    - name: Setup Flyway
      uses: red-gate/setup-flyway@v1
      with:
        version: latest

    - name: Run Flyway Migrate with Callbacks
      run: |
        flyway migrate \
          -url=${{ secrets.DATABASE_URL }} \
          -user=${{ secrets.DATABASE_USER }} \
          -password=${{ secrets.DATABASE_PASSWORD }} \
          -locations="filesystem:db/migration,filesystem:callbacks"

実行結果

このワークフローが実行されると、GitHub Actionsのログには、beforeMigrate.sqlafterMigrate.sqlRAISE NOTICE で出力したメッセージが記録されます。

これにより、マイグレーションプロセスのどの段階が実行されているかを明確に追跡できます。

さらに、INSERT文を使えば、デプロイのたびに監査ログテーブルにレコードを追加することも可能です。


4. より高度なマイグレーション戦略

Flywayの基本的なマイグレーションフローに加えて、より複雑な要件に対応するための高度な戦略が存在します。


4.1 Repeatableマイグレーション活用術(ストアドプロシージャ等の管理)

ビュー、ストアドプロシージャ、関数といったデータベースオブジェクトは、一度作成した後も頻繁に定義が変更されることがあります。

これらの変更をバージョン付きマイグレーション(V*.sql)で管理しようとすると、変更のたびに新しいファイルを作成する必要があり、非常に煩雑です。

この課題を解決するのが Repeatableマイグレーション です。

  • 命名規則: ファイル名を R__<説明>.sql という形式にします。(例: R__Create_or_update_user_view.sql
  • 実行タイミング: バージョン付きマイグレーションが全て適用された に実行されます。
  • 再適用: FlywayはRepeatableマイグレーションのチェックサムを記録しており、ファイルの内容に変更があった場合のみ、そのスクリプトを再適用します。

実践例:ストアドプロシージャの管理

CREATE OR REPLACE PROCEDURE(または同等の構文)を使い、スクリプトを冪等(何度実行しても同じ結果になる)に保つのがベストプラクティスです。

db/migration/R__Update_get_user_summary_procedure.sql:

CREATE OR REPLACE PROCEDURE get_user_summary(IN user_id INT)
BEGIN
    -- プロシージャの最新の定義
    SELECT id, name, last_login FROM users WHERE id = user_id;
END;

このファイルを変更して flyway migrate を実行すると、Flywayがチェックサムの変更を検知し、自動的にプロシージャを最新の状態に更新してくれます。


4.2 Flywayの有償版機能(Teams/Enterprise)

FlywayはオープンソースのCommunity版でも非常に強力ですが、有償版(Teams/Enterprise)では、より大規模でミッションクリティカルな環境向けの高度な機能が提供されます。

機能説明提供エディション
Dry Runs実際のDBに変更を加えず、実行されるSQLスクリプトのレポートを生成する機能。本番適用前の最終確認に絶大な効果を発揮します。Teams / Enterprise
Undo Migrations適用済みのバージョン付きマイグレーションを取り消すための U*.sql スクリプトをサポート。flyway undo コマンドでロールバックできます。Teams / Enterprise
Drift DetectionFlywayが管理するスキーマと、実際のDBスキーマとの間に「ズレ(Drift)」が生じていないかを検出・レポートします。手動変更などを検知するのに役立ちます。Enterprise
Script Generationスキーマの比較結果から、マイグレーションスクリプトを自動生成します。Enterprise

これらの機能が必要になるかどうかは、プロジェクトの規模や運用ポリシーによりますが、どのような機能が存在するかを知っておくことは、ツール選定や将来の拡張において重要です。


4.3 スキーマ履歴テーブルのカスタマイズ:内部管理の最適化

Flywayは、実行されたマイグレーションの履歴を flyway_schema_history というテーブルに記録します。このテーブル名やカラム名をカスタマイズすることで、既存の命名規則に合わせたり、複数のFlywayインスタンスが同じデータベースを共有する際の競合を回避したりできます。

flyway.table プロパティで履歴テーブル名を変更できます。

flyway.table=db_migration_history

4.4 複数スキーマの管理:高度なマルチテナント戦略

flyway.schemas プロパティを使用すると、Flywayが管理するスキーマを複数指定できます。これは、マルチテナントアプリケーションで各テナントが独自のデータベーススキーマを持つ場合に特に有効です。

flyway.schemas=tenant_a_schema,tenant_b_schema,common_schema

この設定により、Flywayは指定されたすべてのスキーマに対してマイグレーションを実行します。


4.5 ベースラインマイグレーション:既存データベースとの統合

既存のデータベースにFlywayを導入する場合、「このバージョンまでのマイグレーションは既に適用済みである」とFlywayに伝えるベースライン (Baseline) 機能が役立ちます。

V3.0までが適用済みである場合、以下のように設定することで、FlywayはV4.0以降からマイグレーションを開始します。

flyway.baselineVersion=3.0
flyway.baselineDescription=Initial Schema

これにより、既存のデータベースを壊すことなく、安全にFlywayの管理下に置くことができます。


4.6 クリーン操作の制御:本番環境での安全策

clean コマンドは、データベーススキーマを完全に削除するため、本番環境での実行は極めて危険です。flyway.cleanDisabled プロパティをtrueに設定することで、このコマンドの実行を禁止できます。

# application-prod.properties (本番環境用設定)
flyway.cleanDisabled=true

この設定を本番環境でのみ有効にすることで、データベースの安全性を確保できます。


5. Testcontainersによる信頼性の高いマイグレーションテスト

Flywayで作成したマイグレーションスクリプトは、当然ながらテストが必要です。しかし、H2のようなインメモリデータベースでテストを行うと、本番環境のデータベース(例: PostgreSQL, MySQL)とのSQL方言の違いから、テストは成功したのに本番デプロイで失敗する、という事態が起こり得ます。

この「環境による差異」問題を根本的に解決するのが Testcontainers です。


5.1 なぜTestcontainersが必要か?

Testcontainersは、JUnitなどのテストフレームワークと連携し、テスト実行時にDockerコンテナとして本物のデータベースをプログラムで起動・破棄してくれるライブラリです。

  • 本番環境との完全な互換性: PostgreSQLでテストすれば、本番もPostgreSQL。SQL方言の心配は無用です。
  • クリーンなテスト環境: 各テストクラス(あるいはテストスイート)ごとに、まっさらなデータベースを起動するため、テスト間のデータ干渉がありません。
  • 宣言的なセットアップ: 特にSpring Bootと組み合わせると、設定ファイルに一行追加するだけで、自動的にデータベースコンテナが起動します。

5.2 実践:Spring Bootと連携したテストコード例

Spring BootアプリケーションでTestcontainersとFlywayを連携させるテストの実装例を見てみましょう。

ステップ1: 依存関係の追加 (pom.xml)

testスコープでspring-boot-starter-test, testcontainers, postgresql (または使用するDB) のライブラリを追加します。

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.testcontainers</groupId>
    <artifactId>junit-jupiter</artifactId>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.testcontainers</groupId>
    <artifactId>postgresql</artifactId>
    <scope>test</scope>
</dependency>

ステップ2: テスト用設定ファイル

src/test/resources/application.properties に、Testcontainersを有効にするための特別なJDBC URLを記述します。

# Testcontainersを有効化し、PostgreSQL 14のコンテナを起動する
spring.datasource.url=jdbc:tc:postgresql:14-alpine:///testdb
# Flywayを有効にする(Spring Bootは自動でマイグレーションを実行)
spring.flyway.enabled=true

これだけで、テスト実行時にTestcontainersがPostgreSQLコンテナを起動し、Spring BootがそのDBに対して自動的にFlywayマイグレーションを実行します。

ステップ3: テストクラスの実装

テストクラスでは、マイグレーションが正しく適用された結果を検証します。

@SpringBootTest
@ActiveProfiles("test")
class UserRepositoryTest {

    @Autowired
    private JdbcTemplate jdbcTemplate;

    @Test
    void testUserTableExistsAndHasData() {
        // FlywayのV1__create_users_table.sqlで作成されたテーブルが存在するか
        Integer userCount = jdbcTemplate.queryForObject("SELECT COUNT(*) FROM users", Integer.class);

        // V2__insert_initial_user.sqlでデータが入っているか
        assertThat(userCount).isGreaterThan(0);

        String userName = jdbcTemplate.queryForObject("SELECT name FROM users WHERE id = 1", String.class);
        assertThat(userName).isEqualTo("admin");
    }
}

5.3 Flywayコールバックをテストする方法

Javaベースのコールバックが正しく動作するかどうかもテストしたい場合があります。Testcontainers環境でこれをテストするには、テスト用のFlywayConfigurationCustomizerを利用するのが便利です。

ステップ1: 動作を検証するためのコールバックを作成

テスト対象のコールバックとは別に、テストでフックするための簡単なコールバックを用意します。(あるいは、テスト対象のコールバックにテスト用のフックを設けます)

// テスト検証用のフラグを持つコールバック
public class TestSpyCallback implements Callback {
    public static boolean afterMigrateCalled = false;

    @Override
    public boolean supports(Event event, Context context) {
        return event == Event.AFTER_MIGRATE;
    }

    @Override
    public boolean canHandleInTransaction(Event event, Context context) {
        return true;
    }

    @Override
    public void handle(Event event, Context context) {
        afterMigrateCalled = true;
    }

    @Override
    public String getCallbackName() {
        return "TestSpyCallback";
    }
}

ステップ2: テストクラスでコールバックを登録し、検証

@SpringBootTest
class FlywayCallbackTest {

    @TestConfiguration
    static class TestConfig {
        // テスト実行時のみ、このカスタマイザーが有効になる
        @Bean
        public FlywayConfigurationCustomizer flywayCustomizer() {
            return configuration -> configuration.callbacks(new TestSpyCallback());
        }
    }

    @BeforeEach
    void setup() {
        // 各テストの前にフラグをリセット
        TestSpyCallback.afterMigrateCalled = false;
    }

    @Test
    void afterMigrateCallbackShouldBeCalled() {
        // SpringBootTestの起動時にFlywayマイグレーションが実行されるので、
        // このテストが走る時点ではコールバックは既に呼ばれているはず。
        assertThat(TestSpyCallback.afterMigrateCalled).isTrue();
    }
}

@TestConfiguration を使うことで、テスト実行時のみ特定のBean(この場合はFlywayConfigurationCustomizer)をDIコンテナに追加し、テスト対象のコールバックがFlywayに登録されるように仕向け、その実行結果を検証できます。


6. FAQ:カスタマイズによる課題解決

Flywayを実践で使い始めると、様々な疑問や課題に直面します。ここでは、開発者が遭遇しがちな典型的な課題と、本記事で紹介したカスタマイズ技術を応用した解決策を解説します。


Q1. チーム開発でマイグレーションのバージョン番号が競合してしまいました。どうすれば良いですか?

これはチーム開発における非常によくある問題です。例えば、2人の開発者がそれぞれローカルでV1.5__add_feature_A.sqlV1.5__add_feature_B.sqlを作成してしまうと、マージ時にバージョンが衝突します。

解決策1:命名規則による予防(ワークフローの改善)

最も効果的なのは、バージョン番号が競合しにくい命名規則をチームで導入することです。タイムスタンプをバージョンに含める(例: V20260110143000__...)のが一般的です。

解決策2:コマンドによる事後対処(高度なカスタマイズ)

もし競合が発生してしまった場合は、一方のブランチでバージョン番号をV1.6などに変更するのが基本です。有償のFlyway Teams Editionではflyway migrate -cherryPick="1.5"のように適用対象を選択できますが、OSS版で利用できるskipExecutingMigrationsパラメータの主目的は「外部で適用済みの変更をFlywayの履歴に反映させる」ことであり、バージョン競合の恒久的な解決策としては推奨されません。


Q2. 一度適用したマイグレーションスクリプトを修正したいのですが…

原則として、一度migrateで適用され、他の環境に共有されたマイグレーションスクリプトは、決して編集してはいけません。 Flywayはスクリプトのチェックサム(内容のハッシュ値)を記録しており、変更を検知するとエラーを出して一貫性を保証します。

解決策1:新しい「修正用」マイグレーションの追加(基本)

最も安全で一般的な方法は、間違いを修正するための新しいマイグレーションスクリプトを追加することです。例えば、V1.5の間違いを正すV1.6__correct_column_type.sqlを作成します。

解決策2:Undoマイグレーションの活用 (Teams版)

有償のFlyway Teams Editionでは、Vプレフィックスに対応するUプレフィックスを持つUndoマイグレーションスクリプトを作成できます。U1.5に変更を取り消すSQLを記述しておけば、flyway undoコマンドで安全にロールバックできます。ただし、データの破壊的な変更(DROP TABLEなど)を伴う操作のUndoは、スクリプトの実装を慎重に行う必要があります。

解決策3:コールバックによるセーフティネットの構築(高度なカスタマイズ)

元に戻せない操作に備え、コールバック機能でセーフティネットを構築できます。例えば、beforeEachMigrate.sqlで特定のスクリプト実行前にテーブルをバックアップする、といった応用が可能です。

-- beforeEachMigrate.sql
-- 特定スクリプト実行前にテーブルをバックアップする例(SQLは擬似コード)
IF( '${flyway.script}' = 'V1.7__drop_old_column.sql' )
  CREATE TABLE users_backup_20260110 AS TABLE users;
END IF;

注意: 上記のSQLは概念を示す擬似コードです。IF文の構文や、flyway.scriptのような変数が利用可能かは、対象データベースのSQL方言やFlywayのバージョンに依存します。


Q3. ゼロダウンタイムデプロイでFlywayを運用するには?

ゼロダウンタイムデプロイ(ZDD)は、Blue/Greenデプロイメントやローリングアップデートといった手法で実現されますが、データベースのスキーマ変更が伴う場合は格段に難易度が上がります。ここでの鉄則は、常に後方互換性のある(Backward-Compatible)変更を行うことです。

新旧バージョンのアプリケーションが、移行期間中に同じデータベースを共有しても問題なく動作できるように、Expand and Contract(拡張・縮小) と呼ばれる多段階のリリース戦略を取るのが一般的です。

  1. Expand(拡張)フェーズ:
    • カラムの追加、テーブルの追加など、非破壊的な変更のみを含むマイグレーションを適用します。カラムの削除やリネームはまだ行いません。
    • 古いバージョンのアプリは、新しいカラムやテーブルを単に無視します。
  2. Migrate(移行)フェーズ:
    • 新しいバージョンのアプリケーションをデプロイします。このアプリは、新旧両方のスキーマ構造を認識できるように実装します。
    • 例えば、データの書き込みは新旧両方のカラムに行い、読み込みはまだ古いカラムから行う、といった実装(Dual Write)が考えられます。
  3. Contract(縮小)フェーズ:
    • 全てのアプリケーションが新しいバージョンに切り替わり、安定稼働が確認された後、古いカラムやテーブルを削除するための新しいマイグレーションスクリプトを適用します。

この段階的なアプローチにより、アプリケーションのダウンタイムを発生させることなく、安全にデータベーススキーマを進化させることができます。


Q4. マイクロサービスアーキテクチャでのスキーマ管理はどうすれば良い?

マイクロサービスアーキテクチャでFlywayを運用する際の最も重要な設計原則は 「Database per Service(サービスごとにデータベースを持つ)」 です。

理想的なパターン:Database per Service

  • 各マイクロサービスは、自身がオーナーである専用のデータベース(またはスキーマ)を持ちます。
  • Flywayのマイグレーションスクリプトは、そのサービスのコードリポジトリ内で一緒に管理されます。
  • これにより、あるサービスのスキーマ変更が他のサービスに影響を与えることがなくなり、チームは自律的に開発・デプロイを進めることができます。

避けるべきアンチパターン:共有データベース

  • 複数のマイクロサービスが単一のデータベースを共有すると、以下のような問題が発生し、マイクロサービスの利点が失われます。
    • 密結合: あるサービスのスキーマ変更が、他のサービスに予期せぬ不具合を引き起こす可能性があります。
    • デプロイの競合: どのサービスがどのタイミングでマイグレーションを適用するかの調整が複雑になります。
    • バージョン管理の衝突: Q1で述べたような、マイグレーションのバージョン番号の競合が頻発しやすくなります。

プロジェクトの初期段階で「Database per Service」パターンを採用することが、長期的に見てシステムの保守性・拡張性を大きく向上させます。


7. まとめ

本記事では、Flywayの基本的な使い方から一歩進んで、設定のカスタマイズ、プレースホルダー、コールバックといった応用機能に加え、RepeatableマイグレーションやTestcontainersによるテスト戦略、ゼロダウンタイムデプロイといった、クラウドネイティブ時代に求められる高度なテクニックまで、幅広く解説しました。

Flywayはシンプルなツールですが、これらの応用的な機能を理解し活用することで、複雑なプロジェクト要件やチーム開発の課題にも、堅牢かつ柔軟に対応できるようになります。

今回紹介したテクニックを、ぜひご自身のプロジェクトで試し、より安全で効率的なデータベースマイグレーションを実現してください。


免責事項

  • 本記事の内容は、2026年1月時点の情報と、Flyway v10を基準にしています。Flyway本体や関連ライブラリのバージョンアップにより、コマンド、パラメータ、APIの仕様が変更される可能性があります。
  • 本記事で紹介するコードやコマンドの実行は、ご自身の責任において、十分にテストされた環境で行ってください。特に本番環境での操作には細心の注意を払ってください。
  • 正確な情報を提供するよう努めていますが、常に公式サイトのドキュメントを併せて参照することをお勧めします。

参考資料

本記事を作成するにあたり、以下の公式ドキュメントや技術ブログを参考にしました。より深く学ぶための出発点としてご活用ください。


SNSでもご購読できます。

コメントを残す

*