
データベースのスキーマ変更は、どのように管理されていますか?
多くの開発現場で、以下のような課題が聞かれます。
- 「手動でSQLを実行したら、本番環境と開発環境でスキーマに差異が生じてしまった」
- 「どのSQLスクリプトが適用済みなのか分からなくなり、確認作業に多くの時間を費やしてしまう」
データベースのバージョン管理は、多くの開発チームにとって共通の課題です。
その解決策としてFlywayを導入し、変更履歴をバージョン管理することは、この問題に対処する重要な第一歩となります。
しかし、チームでの開発が本格化し、CI/CDが浸透した現代の開発プロセスにおいては、さらに進んだ課題が生じます。
- 「他の開発者の変更とコンフリクトしてしまった場合、どのように解決すればよいのか?」
- 「作成したマイグレーションスクリプトは本当に安全なのか?どのようにテストすれば品質を保証できるのか?」
- 「デプロイを実行する前に、本番データベースとの差分を自動でチェックできないか?」
この記事では、Flywayの基本的な使い方を習得した開発者を対象に、一歩先のレベルへ進むための実践的なテクニックを解説します。Flywayを単に「使う」だけでなく、それを中心とした自動化された安全なワークフローを構築することを目的としています。
この記事で学べること
- Flywayマイグレーションスクリプトの基本的な書き方と命名規則
- チーム開発を円滑にするための、スクリプト管理のベストプラクティス
- ORM(JPA/Hibernate)とFlywayを安全に共存させる設定方法
Testcontainersを活用した、マイグレーションの自動テスト手法- CI/CDパイプラインにFlywayを組み込み、変更プロセスを自動化する具体的なフロー
目次
- 1. マイグレーションスクリプトの基本ルール
- 2. マイグレーションの種類と使い分け
- 3. スクリプト管理のベストプラクティス
- 4. チーム開発とCI/CDパイプライン
- 5. マイグレーションのテストと高度な活用法
- まとめ
- 参考資料
対象読者
- Flywayの基本的な使い方(
migrateコマンドなど)は理解している方 - チームでのデータベース開発において、コンフリクトや手戻りに課題を感じている方
- マイグレーションスクリプトの品質管理やテスト方法を模索している方
- CI/CDパイプラインにデータベースの変更管理を組み込み、プロセスを自動化したい方
1. マイグレーションスクリプトの基本ルール
Flywayを効果的に活用するためには、基本となるルールを正確に理解することが重要です。
このセクションでは、Flywayがマイグレーションを正しく認識・実行するために不可欠な「命名規則」と「SQLの書き方」について解説します。
1.1. 命名規則の重要性
Flywayのマイグレーションスクリプトのファイル名は、その役割を定義する重要な情報を含んでいます。
Flywayはファイル名に付けられた規約を解析し、マイグレーションの種類、バージョン、説明を読み取って実行計画を立てるためです。
Flywayが認識する主要な命名規則は、以下の構造で構成されます。
<Prefix><Version>__<Description>.sql- Prefix: マイグレーションの種類を示します。
V: Versioned Migrations(バージョン管理マイグレーション)。最も一般的に使用されます。一度適用されると、再度実行されることはありません。主にテーブル作成やカラム追加といったスキーマ構造の変更に使用します。R: Repeatable Migrations(リピータブルマイグレーション)。バージョンを持たず、ファイルの内容(チェックサム)が変更されるたびに再実行されます。ビューやストアドプロシージャの定義など、繰り返し適用したいSQLの管理に適しています。U: Undo Migrations(Undoマイグレーション)。特定のバージョンのマイグレーションを取り消すSQLを定義します。この機能は有償版である Flyway Teams/Enterprise でのみ利用可能です。B: Baseline Migrations(ベースラインマイグレーション)。既存のデータベースにFlywayを導入する際に、その時点のスキーマ状態を基準(ベースライン)として設定するために使用します。flyway baselineコマンドと合わせて使われます。
- Version: マイグレーションのバージョン番号です。
.(ドット) や_(アンダースコア) で区切られた数字で構成され、時系列で昇順になるように採番します。(例:1.0.0,2.1.3)。タイムスタンプ(例:20260110143000)をバージョンとして利用すると、チーム開発でのバージョン番号の競合を避けやすくなります。 - Separator:
__(ダブルアンダースコア)で、バージョンと説明を区切ります。 - Description: マイグレーションの内容を示す、アンダースコアで区切られた説明です。(例:
Create_user_table) - Suffix:
.sql
ファイル名の例:
/* 一般的なバージョン管理マイグレーション */
V1.0.0__Create_users_table.sql
/* タイムスタンプをバージョンとして利用したマイグレーション */
V20260110143000__Add_email_column_to_users.sql
/* 繰り返し実行されるリピータブルマイグレーション */
R__Create_or_update_user_summary_view.sql
/* V1.0.0の変更を取り消すためのUndoマイグレーション(有償版限定) */
U1.0.0__Create_users_table.sql例えば、V1.0.1__Add_email_column_to_user_table.sql というファイル名は、「バージョン1.0.1の、userテーブルにemailカラムを追加するSQLスクリプト」としてFlywayに解釈されます。
1.2. SQLスクリプトの書き方(DDL, DML)
FlywayのSQLスクリプトには、基本的にどのようなSQLでも記述できます。
スクリプトに記述するSQLは、大きく分けてDDL (Data Definition Language) と DML (Data Manipulation Language) の2種類に分類されます。
- DDL: データベースの構造(スキーマ)を定義・変更するためのSQLです。
- 例:
CREATE TABLE,ALTER TABLE,DROP TABLE
- 例:
- DML: データを操作(追加、更新、削除)するためのSQLです。
- 例:
INSERT,UPDATE,DELETE
- 例:
バージョン管理マイグレーション(Vスクリプト) では、主にDDLを使ってスキーマの構造を段階的に変更します。
一方、リピータブルマイグレーション(Rスクリプト) では、DMLを使ってマスターデータを登録したり、DDLを使ってビューを最新の状態に更新したりといった用途で使われることが一般的です。
注意点: DDLとトランザクション
データベース製品(例: MySQL, Oracle, SQL Server)によっては、DDL文がトランザクションを自動的にコミットする仕様になっています。このようなデータベースでは、1つのマイグレーションスクリプト内に複数のDDL文を記述して実行すると、途中でエラーが発生した場合にロールバックが効かず、一部の変更だけが適用された不整合な状態に陥る危険性があります。
2. マイグレーションの種類と使い分け
Flywayにはいくつかのマイグレーションタイプがあり、それぞれの役割を理解して使い分けることが重要です。
2.1. バージョン管理マイグレーション (Versioned Migrations)
Flywayの最も基本的なマイグレーションです。
ファイル名に含まれるバージョン番号に基づき、一度だけ実行されます。テーブルの作成、カラムの追加・削除・変更など、スキーマを段階的に進化させるための主要な手段となります。このような変更は、一度適用すると元に戻すのが難しい、あるいは不可逆的な性質を持ちます。
-- V1.0.0__Create_user_table.sql
CREATE TABLE users (
id INT PRIMARY KEY,
name VARCHAR(100) NOT NULL
);2.2. リピータブルマイグレーション (Repeatable Migrations)
バージョンを持たず、ファイルの内容(チェックサム)が変更されるたびに再実行されるマイグレーションです。
ビュー、ストアドプロシージャ、トリガーなど、定義を常に最新の状態に保ちたいデータベースオブジェクトの管理に非常に便利です。
例えば、開発中にビューの定義を頻繁に変更する場合、その都度新しいバージョンファイルを作成するのは非効率です。Rスクリプトを使えば、1つのファイルを修正するだけで、Flywayが自動で変更を検知して再適用してくれます。
-- R__Create_or_update_user_view.sql
CREATE OR REPLACE VIEW user_view AS
SELECT id, name FROM users;2.3. Undoマイグレーション (Undo Migrations)
適用済みのバージョン管理マイグレーションを元に戻すためのSQLを記述するのが、Undoマイグレーションです。デプロイ後に問題が発覚し、緊急で変更をロールバックする必要があるシナリオで役立ちます。
ただし、この機能はFlyway Teams/Enterprise版限定の機能である点に注意が必要です。
また、データの損失を発生させずにスキーマを安全にダウングレードするロジックを設計する必要があり、その作成と管理には相応のコストがかかります。そのため、全てのバージョンに対してUndoスクリプトを用意するのではなく、重要な変更やリスクの高い変更に限定して作成するのが現実的なアプローチです。
-- U1.0.1__Add_email_column_to_user_table.sql
-- Flyway Teams/Enterprise Edition Only
ALTER TABLE users DROP COLUMN email;2.4. ベースラインマイグレーション (Baseline Migrations)
すでに稼働しているデータベースに、途中からFlywayを導入する際に使用するのが「ベースラインマイグレーション」の考え方です。
flyway baseline コマンドを実行すると、Flywayは指定されたバージョンまでのマイグレーションが「すでに適用済みである」という記録を自身の履歴テーブル(flyway_schema_history)に作成します。
これにより、既存のスキーマ状態をFlywayの管理下に置き、それ以降の新しい変更からバージョン管理をスムーズに開始できます。
# "1.5"というバージョンを基準として設定する例
flyway baseline -version="1.5" -description="Add Flyway to existing schema"このコマンドを実行すると、バージョン 1.5 までのマイグレーションは実行されず、次に flyway migrate を実行した際には V1.6__... 以降のスクリプトからマイグレーションが適用されるようになります。
3. スクリプト管理のベストプラクティス
スクリプトの書き方を理解したら、次はそれらをチームで効果的に「管理」する方法を見ていきましょう。
特にチーム開発では、管理ルールが曖昧だと、変更の衝突や依存関係の問題が発生しやすくなります。
3.1. 「1マイグレーション1変更」の原則
1つのマイグレーションスクリプトには、関連性の高い1つの論理的な変更のみを含めるのが理想です。
例えば、「ユーザーテーブルにemailカラムを追加する」という変更と、「ブログ記事テーブルを作成する」という変更は、関心事が異なるため、別々のファイルに分割します。
この原則には、以下のようなメリットがあります。
- 可読性の向上: スクリプトの目的が明確になり、コードの意図を理解しやすくなります。
- 問題解決の容易化: マイグレーションで問題が発生した際、原因となる変更範囲を特定しやすくなります。
- コンフリクトの低減: 複数の開発者が並行して作業する際に、変更が衝突する可能性を減らせます。
3.2. コメントの活用
SQLスクリプトには、その変更が「なぜ」必要なのか、設計上の意図は何かを説明するコメントを残すことが重要です。
コードは「何をしているか」を示しますが、コメントは「なぜそうしているか」を補足します。
特に、複雑なロジックや、後から見たときに意図が分かりにくい可能性のある変更には、将来の自分やチームメンバーのために、必ずコメントを残すようにしましょう。
3.3. スクリプトのレビュープロセス
マイグレーションスクリプトも、アプリケーションコードと同様に、コードレビューの対象とすることが不可欠です。
レビュープロセスを通じて、以下のような点をチームで確認します。
- 命名規則は規約に沿っているか
- SQLの構文は正しいか
- パフォーマンスに深刻な影響を与えるクエリはないか
- 変更内容はビジネス要件と一致しているか
- 既存のデータや機能への影響(後方互換性、データ損失リスク)は考慮されているか
3.4. プレースホルダーで環境による差分を吸収する
開発、ステージング、本番といった環境ごとに、接続情報やユーザー名などが異なることは一般的です。Flywayは、このような環境差を吸収するためにプレースホルダー機能を提供しています。
flyway.conf ファイルや環境変数でプレースホルダーの値を定義し、SQLスクリプト内で ${placeholderName} のように記述すると、実行時にその値が置換されます。
例えば、環境ごとに設定ファイルを用意し、実行時に読み込むファイルを切り替える運用が可能です。
flyway-dev.conf (開発環境用)
flyway.placeholders.target_tablespace=DEV_DATA_01flyway-prod.conf (本番環境用)
flyway.placeholders.target_tablespace=PROD_DATA_ASQLスクリプトの例
-- V3__Create_orders_table.sql
CREATE TABLE orders (
id INT PRIMARY KEY
) TABLESPACE ${target_tablespace};この設定により、SQLスクリプト自体は変更せず、実行コマンドで読み込む設定ファイルを指定するだけで、環境に応じた設定を適用できます。
# 開発環境でマイグレーションを実行
flyway -configFile=flyway-dev.conf migrate
# 本番環境でマイグレーションを実行
flyway -configFile=flyway-prod.conf migrate3.5. 大規模プロジェクトのためのファイル構成戦略
プロジェクトが大規模になるとマイグレーションスクリプトの数が増加し、デフォルトの db/migration ディレクトリが肥大化しがちです。
このような場合は、関連機能やドメインごとにサブディレクトリへ分割すると、管理性が向上します。
src/main/resources/db
├── common # 共通テーブルや基本設定
│ └── V1__Create_users_table.sql
├── feature_billing # 請求機能関連
│ ├── V2__Create_invoices_table.sql
│ └── R__Update_billing_summary_view.sql
└── feature_shipping # 配送機能関連
└── V3__Create_addresses_table.sqlこの構成を有効にするには、flyway.locations プロパティでスキャン対象のディレクトリをカンマ区切りで複数指定します。
# flyway.conf
# flyway.locationsプロパティでスキャン対象のパスを複数指定します。
# デフォルトは classpath:db/migration です。
# filesystem: プレフィックスは、ファイルシステム上のパスを示します。
flyway.locations=filesystem:src/main/resources/db/common,filesystem:src/main/resources/db/feature_billing,filesystem:src/main/resources/db/feature_shipping3.6. トランザクション管理とエラーからの回復性
Flywayはデフォルトで、1つのマイグレーションスクリプトを単一のトランザクション内で実行します。
これにより、スクリプトの途中でエラーが発生した場合に変更全体がロールバックされ、データベースが中途半端な状態になるのを防ぎます。
しかし、前述の通り、一部のデータベース(MySQL, Oracleなど)ではDDL文が暗黙的にトランザクションをコミットします。
このようなデータベース製品では、1つのスクリプトに複数のDDL文が含まれていると、エラー発生時にロールバックが機能せず、データベースが不整合な状態に陥るリスクがあります。
対策:
- マイグレーションを小さく保つ: 「1マイグレーション1変更の原則」を遵守し、1ファイルあたりの変更範囲を小さく保ちます。これにより、失敗時の影響を局所化できます。
executeInTransactionの理解: どうしてもトランザクション外で実行したいステートメントがある場合、Flywayのこの設定項目について理解した上で利用を検討します。しかし、基本的にはデフォルト設定(有効)のまま運用するのが最も安全です。
4. チーム開発とCI/CDパイプライン
Flywayは、個人の開発環境だけでなく、チーム開発のワークフローやCI/CDパイプラインに組み込むことで、その真価を発揮します。
4.1. 開発中の競合解決とワークフロー
ローカル環境でまだ誰とも共有していないマイグレーションスクリプトは、自由に修正や削除が可能です。しかし、一度チームメンバーと共有(例: Gitのリモートリポジトリにプッシュ)したマイグレーションは、原則として変更してはいけません。
ファイルの内容が変わるとチェックサムも変わり、他のメンバーの環境でマイグレーションエラーが発生するためです。
もし共有済みのマイグレーションを修正する必要が生じた場合は、安易に既存ファイルを変更するのではなく、その変更を取り消すか修正するための新しいバージョンのマイグレーションスクリプトを作成して対応します。
一般的な開発ワークフローは以下のようになります。
- Featureブランチで開発中、
flyway cleanとflyway migrateを繰り返してスクリプトを試行錯誤します。 - 開発が完了したら、
mainブランチにマージする前に、最新のmainブランチの内容をリベース(git rebase)します。 - リベースによって他の開発者の変更を取り込み、必要であればバージョン番号の衝突を解決してから
mainブランチにマージします。

前述の通り、バージョン番号にタイムスタンプ(例: 20260110143000)を使用すると、チーム内でのバージョン番号の衝突を効果的に防ぐことができます。
4.2. ORM (JPA/Hibernate) との安全な連携方法
多くのJavaプロジェクトでは、JPA/HibernateのようなORM(Object-Relational Mapping)が利用されています。
これらのORMが持つスキーマ自動生成機能(例: hibernate.hbm2ddl.auto = "update")は、開発初期には便利ですが、Flywayとの併用は避けるべきです。両者がそれぞれスキーマを変更しようとするため、管理が二重になり、予期せぬ問題の原因となります。
ベストプラクティス:
- データベーススキーマの変更は、Flywayが唯一の信頼できるソース(Single Source of Truth)となるようにします。
hibernate.hbm2ddl.autoの設定をvalidateに変更します。- この設定により、アプリケーション起動時にJPAエンティティの定義と実際のデータベーススキーマとの間に差異がないかが検証されます。もし差異があれば、アプリケーションはエラーを出して起動に失敗します。
- この仕組みによって、「マイグレーションスクリプトの適用漏れ」や「エンティティ定義とスキーマの不整合」といった問題を早期に検知できます。
4.3. CI/CDへの統合:flyway checkでデプロイ前の問題を検知する
CI/CDパイプラインにFlywayを組み込むことで、データベース変更の適用プロセスを自動化し、安全性を大きく向上させることができます。
ここで特に有効なのが flyway check コマンドです。
check コマンドは、適用済みのマイグレーションと現在利用可能なマイグレーションスクリプト群を比較・分析し、以下のような問題をデプロイ前に検出してくれます。
- 適用済みのマイグレーションが(誤って)変更・削除されている
- 適用済みのマイグレーションより古いバージョンのマイグレーションが追加されている
- (Flyway Teams/Enterprise版) スキーマのドリフト(Flywayを介さずに行われた手動変更)の検知
CIパイプラインの概念例(GitHub Actions):
jobs:
deploy-to-staging:
runs-on: ubuntu-latest
steps:
# ... (コードのチェックアウトなど) ...
- name: Build and Test Migrations
run: ./flyway -url=jdbc:h2:mem:build_db migrate # ビルド時にインメモリDBでマイグレーションの構文妥当性をテスト
- name: Check for Drift against Staging DB
run: ./flyway -url=${{ secrets.STAGING_DB_URL }} check # ステージングDBに対して問題を事前チェック
# ... (チェックが成功した場合のみアプリケーションをデプロイ) ...この例のように、ビルド時にインメモリDBでマイグレーションの基本的なテストを行い、デプロイ先の環境に対して check を実行することで、より安全なデプロイフローを構築できます。
5. マイグレーションのテストと高度な活用法
マイグレーションスクリプトは、アプリケーションを構成する重要なコードの一部です。その品質を保証するために、テストを導入することが重要になります。
5.1. Testcontainersでマイグレーションをテストする
Testcontainersは、テスト実行時にDockerコンテナ(データベースなど)をプログラムで制御できるJavaライブラリです。
これとFlywayを組み合わせることで、毎回クリーンなデータベースインスタンスに対してマイグレーションを実行し、その正しさを自動テストできます。
JUnit 5でのテストコード例:
import org.flywaydb.core.Flyway;
import org.junit.jupiter.api.Test;
import org.testcontainers.containers.PostgreSQLContainer;
import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers;
@Testcontainers
class MigrationTest {
@Container
private static final PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:15-alpine");
@Test
void testMigrationsCanBeAppliedCleanly() {
// Testcontainersが起動したDBコンテナの情報を使ってFlywayを構成します
Flyway flyway = Flyway.configure()
.dataSource(postgres.getJdbcUrl(), postgres.getUsername(), postgres.getPassword())
.load();
// マイグレーションを実行します
flyway.migrate();
// ここでさらに、JDBCやJPAを使い、
// テーブルやカラムが期待通りに作成されたかなどをアサート(検証)する
// テストを追加することも可能です
}
}このテストをCI(継続的インテグレーション)プロセスに組み込むことで、マイグレーションスクリプトに構文エラーなどが含まれていないことを常に保証できます。
5.2. コールバックで定型処理を自動化する (SQL & Java)
Flywayは、マイグレーションのライフサイクルの特定のタイミングで、決まった処理を自動実行させるコールバック機能を提供しています。
- SQLコールバック:
beforeMigrate.sqlやafterEachMigrate.sqlのような規約に沿った名前でSQLファイルを配置すると、対応するイベントで自動的に実行されます。例えば、テスト実行時に、全マイグレーション適用後にテストデータを投入する、といった用途で活用できます。 - Javaコールバック:
org.flywaydb.core.api.callback.Callbackインターフェースを実装したJavaクラスを作成することで、より複雑なロジックを記述できます。例えば、特定のマイグレーションが成功した後に、関連するシステムのキャッシュをクリアするAPIを呼び出す、といった処理が可能です。
まとめ
この記事では、Flywayにおけるマイグレーションスクリプトの書き方と管理について、基本的なルールからチーム開発、CI/CD、テストといった実践的なベストプラクティスまでを解説しました。
- 命名規則を正しく理解し、
V,R,Bなどの種類を適切に使い分けます。 - 「1マイグレーション1変更」の原則を守り、プレースホルダーやファイル構成戦略で保守性を高めます。
- スクリプトもコードレビューの対象とし、Testcontainersによる自動テストで品質を担保します。
- チーム開発では、共有済みスクリプトの変更を避け、ORMとは
validateモードで安全に連携させます。 - CI/CDパイプラインに
flyway checkを組み込み、デプロイ前に問題を自動検知する仕組みを構築します。
これらのプラクティスを実践することで、データベース変更管理の信頼性を高め、開発チーム全体の生産性を向上させることができます。
次のステップ
Flywayには、今回紹介した以外にもさらに高度な機能があります。次のステップとして、ゼロダウンタイムデプロイメントを実現するためのマイグレーション戦略(Expand/Contractパターンなど)や、Kubernetes/サーバーレスといったクラウドネイティブ環境での実行パターンについて学習を進めることをお勧めします。
免責事項
この記事で紹介するコマンドやコード例は、特定のバージョンや環境に基づいています。お使いの環境やバージョンによっては、コマンドのオプションや挙動が異なる場合があります。実行する前に、必ず公式ドキュメントで最新の情報を確認してください。
参考資料
本記事で解説した内容は、以下の公式ドキュメントでより詳しく確認できます。これまでの内容の裏付けとなった情報源でもあります。
- Flyway Documentation (全般)
