§データベースエボリューションの管理
リレーショナルデータベースを使用する場合、データベーススキーマの進化を追跡および整理する方法が必要です。通常、データベーススキーマの変更を追跡するためのより洗練された方法が必要な状況がいくつかあります。
- 開発者のチーム内で作業する場合、各メンバーはスキーマの変更について知っておく必要があります。
- 本番サーバーにデプロイする場合、データベーススキーマをアップグレードするための堅牢な方法が必要です。
- 複数のマシンで作業する場合、すべてのデータベーススキーマを同期しておく必要があります。
§エボリューションの有効化
依存関係リストにevolutions
とjdbc
を追加します。たとえば、build.sbt
では次のようになります。
libraryDependencies ++= Seq(evolutions, jdbc)
§コンパイル時DIを使用したエボリューションの実行
コンパイル時依存性注入を使用している場合、EvolutionsComponents
トレイトをケーキにミックスインして、インスタンス化されたときにエボリューションを実行するApplicationEvolutions
にアクセスする必要があります。EvolutionsComponents
には、dbApi
が定義されている必要があります。これは、DBComponents
とHikariCPComponents
をミックスインすることで取得できます。applicationEvolutions
はEvolutionsComponents
によって提供される遅延valであるため、エボリューションが実行されるように、そのvalにアクセスする必要があります。たとえば、ApplicationLoader
で明示的にアクセスするか、別のコンポーネントからの明示的な依存関係を持つことができます。
モデルは、データベースへの接続を確立するために、Database
のインスタンスを必要とします。これはdbApi.database
から取得できます。
import play.api.db.evolutions.EvolutionsComponents
import play.api.db.DBComponents
import play.api.db.Database
import play.api.db.HikariCPComponents
import play.api.routing.Router
import play.api.ApplicationLoader.Context
import play.api.BuiltInComponentsFromContext
import play.filters.HttpFiltersComponents
class AppComponents(cntx: Context)
extends BuiltInComponentsFromContext(cntx)
with DBComponents
with EvolutionsComponents
with HikariCPComponents
with HttpFiltersComponents {
// this will actually run the database migrations on startup
applicationEvolutions
}
§エボリューションスクリプト
Playは、複数のエボリューションスクリプトを使用してデータベースのエボリューションを追跡します。これらのスクリプトはプレーンなSQLで記述されており、デフォルトではアプリケーションのconf/evolutions/{データベース名}
ディレクトリに配置する必要があります。エボリューションがデフォルトのデータベースに適用される場合、このパスはconf/evolutions/default
です。
最初のスクリプトの名前は1.sql
、2番目のスクリプトは2.sql
というように続きます...
各スクリプトには2つの部分が含まれています。
- 必要な変換を記述するUps部分。
- それらを元に戻す方法を記述するDowns部分。
たとえば、基本的なアプリケーションをブートストラップするこの最初の進化スクリプトを見てください。
-- Users schema
-- !Ups
CREATE TABLE User (
id bigint(20) NOT NULL AUTO_INCREMENT,
email varchar(255) NOT NULL,
password varchar(255) NOT NULL,
fullname varchar(255) NOT NULL,
isAdmin boolean NOT NULL,
PRIMARY KEY (id)
);
-- !Downs
DROP TABLE User;
Ups部分とDowns部分は、それぞれ!Ups
または!Downs
を含む標準の単一行SQLコメントを使用して区切られます。SQL92(--
)とMySQL(#
)のコメントスタイルがサポートされていますが、より多くのデータベースでサポートされているSQL92構文を使用することをお勧めします。
Playは、
.sql
ファイルをセミコロンで区切られた一連のステートメントに分割し、それらを1つずつデータベースに対して実行します。したがって、ステートメント内でセミコロンを使用する必要がある場合は、;
の代わりに;;
と入力してエスケープします。たとえば、INSERT INTO punctuation(name, character) VALUES ('semicolon', ';;');
です。
データベースがapplication.conf
で構成されており、エボリューションスクリプトが存在する場合、エボリューションは自動的にアクティブ化されます。play.evolutions.enabled=false
を設定すると、エボリューションを無効にできます。たとえば、テストが独自のデータベースを設定する場合、テスト環境のエボリューションを無効にできます。
エボリューションがアクティブ化されると、PlayはDEVモードでは各リクエストの前に、PRODモードではアプリケーションの起動前に、データベーススキーマの状態を確認します。DEVモードでは、データベーススキーマが最新でない場合、エラーページで適切なSQLスクリプトを実行してデータベーススキーマを同期することを提案します。
SQLスクリプトに同意する場合は、「エボリューションを適用」ボタンをクリックして直接適用できます。
§エボリューションの設定
エボリューションは、グローバルとデータソースごとの両方で設定できます。グローバル設定の場合、キーにはplay.evolutions
というプレフィックスを付ける必要があります。データソースごとの設定の場合、キーにはplay.evolutions.db.<データソース名>
というプレフィックスを付ける必要があります。たとえば、play.evolutions.db.default
です。次の構成オプションがサポートされています。
enabled
- エボリューションを有効にするかどうか。グローバルにfalseに設定されている場合、エボリューションモジュール全体が無効になります。デフォルトはtrueです。schema
- 生成されたエボリューションとロックテーブルが保存されるデータベーススキーマ。デフォルトではスキーマは設定されていません。metaTable
- エボリューションのメタデータを保存するテーブル。デフォルトではplay_evolutions
です。autocommit
- オートコミットを使用するかどうか。falseの場合、エボリューションは1つのトランザクションで適用されます。デフォルトはtrueです。useLocks
- ロックテーブルを使用するかどうか。これは、エボリューションを実行する可能性のあるPlayノードが多数あるが、1つだけが実行されるようにしたい場合に、使用する必要があります。これは、エボリューションメタデータテーブルと同じ名前で、_lock
のポストフィックスが付いたテーブル(デフォルトではplay_evolutions_lock
)を作成し、SELECT FOR UPDATE NOWAIT
またはSELECT FOR UPDATE
を使用してロックします。これは、Postgres、Oracle、およびMySQL InnoDBでのみ機能します。他のデータベースでは機能しません。デフォルトはfalseです。autoApply
- エボリューションを自動的に適用するかどうか。開発モードでは、アップエボリューションとダウンエボリューションの両方が自動的に適用されます。本番モードでは、アップエボリューションのみが自動的に適用されます。デフォルトはfalseです。autoApplyDowns
- ダウンエボリューションを自動的に適用するかどうか。本番モードでは、ダウンエボリューションが自動的に適用されます。開発モードでは効果はありません。デフォルトはfalseです。path
- クラスパスまたはファイルシステム上のエボリューションスクリプトへのパス。デフォルトはevolutions
です。「エボリューションスクリプトの場所」を参照してください。substitutions.mappings
- 変数(プレフィックスとサフィックスなし)とその置換のマッピング。デフォルトではマッピングは設定されていません。「変数の置換」を参照してください。substitutions.prefix
- プレースホルダー構文に使用されるプレフィックス。デフォルトは$evolutions{{{
です。「変数の置換」を参照してください。substitutions.suffix
- プレースホルダー構文に使用されるサフィックス。デフォルトは}}}
です。「変数の置換」を参照してください。substitutions.escapeEnabled
- 構文!$evolutions{{{...}}}
による変数のエスケープを有効にするかどうか。デフォルトはtrue
です。「変数の置換」を参照してください。
たとえば、すべてのエボリューションに対してautoApply
を有効にするには、application.conf
またはシステムプロパティでplay.evolutions.autoApply=true
を設定します。default
という名前のデータソースのオートコミットを無効にするには、play.evolutions.db.default.autocommit=false
を設定します。
§エボリューションスクリプトの場所
すでに述べたように、エボリューションスクリプトはデフォルトでアプリケーションのconf/evolutions/{データベース名}
ディレクトリにあります。ただし、フォルダーの名前を変更することはできます。たとえば、デフォルトデータベースのスクリプトをconf/db_migration/default
に保存する場合は、path
設定を設定することで実行できます。
# For the default database
play.evolutions.db.default.path = "db_migration"
# Or, if you want to set the location for all databases
play.evolutions.db.path = "db_migration"
conf
フォルダーの内容は開発モードと本番環境の両方でクラスパス上にあるため、Playは常にこれらのスクリプトをロードできます。
エボリューションスクリプトをプロジェクトフォルダーの外に保存し、絶対パスまたは相対パスで参照することもできます。
# Absolute path:
play.evolutions.db.default.path = "/opt/db_migration"
# Relative path (as seen from your project's root folder)
play.evolutions.db.default.path = "../db_migration"
ただし、このような構成を使用する場合、バンドルを決定した場合、エボリューションスクリプトは本番パッケージに含まれないため、本番環境でのエボリューションスクリプトの管理はあなた次第であることに注意してください。
最後に、プロジェクト内であっても、conf
フォルダーの外にエボリューションスクリプトを保存できます。
play.evolutions.db.default.path = "./db_migration"
その場合、スクリプトはクラスパス上にありません。開発中は、クラスパス上のエボリューションスクリプトを探す前に、Playがファイルシステムでそれらを検索するため、問題ありません(上記で絶対パスと相対パスのアプローチが機能するのはそのためです)。
ただし、evolution
フォルダをconf
ディレクトリに配置せず、クラスパスにも含めない場合、本番環境向けにパッケージングされません。フォルダがパッケージングされ、本番環境で利用できるようにするためには、build.sbt
で設定する必要があります。
Universal / mappings ++= (baseDirectory.value / "db_migration" ** "*").get.map {
(f: File) => f -> f.relativeTo(baseDirectory.value).get.toString
}
§変数置換
evolutionsスクリプト内でプレースホルダーを定義できます。これは、application.conf
で定義された置換値に置き換えられます。
play.evolutions.db.default.substitutions.mappings = {
table = "users"
name = "John"
}
以下のようなevolutionsスクリプトがあるとします。
INSERT INTO $evolutions{{{table}}}(username) VALUES ('$evolutions{{{name}}}');
これは、
INSERT INTO users(username) VALUES ('John');
evolutionsが適用される際に、このようになります。
evolutionsのメタテーブルには、プレースホルダーが置換されていない、生のSQLスクリプトが格納されます。
変数置換は大文字小文字を区別しません。したがって、$evolutions{{{NAME}}}
は$evolutions{{{name}}}
と同じです。
プレースホルダー構文のプレフィックスとサフィックスも変更できます。
# Change syntax to @{...}
play.evolutions.db.default.substitutions.prefix = "@{"
play.evolutions.db.default.substitutions.suffix = "}"
evolutionsモジュールには、変数を置換したくない場合に備えて、エスケープのサポートも付属しています。このエスケープメカニズムはデフォルトで有効になっています。無効にするには、次のように設定する必要があります。
play.evolutions.db.default.substitutions.escapeEnabled = false
有効になっている場合、!$evolutions{{{...}}}
という構文を使用して、変数置換をエスケープできます。たとえば、
INSERT INTO notes(comment) VALUES ('!$evolutions{{{comment}}}');
これは置換値に置き換えられず、代わりに
INSERT INTO notes(comment) VALUES ('$evolutions{{{comment}}}');
最終的なSQLになります。
このエスケープメカニズムは、
substitutions.mappings
設定で変数のマッピングが定義されているかどうかに関係なく、すべての!$evolutions{{{...}}}
プレースホルダーに適用されます。
§同時変更の同期
ここで、このプロジェクトで2人の開発者が作業していると想像してみましょう。Jamieは新しいデータベーステーブルを必要とする機能に取り組むこととします。したがって、Jamieは次の2.sql
evolutionスクリプトを作成します。
-- Add Post
-- !Ups
CREATE TABLE Post (
id bigint(20) NOT NULL AUTO_INCREMENT,
title varchar(255) NOT NULL,
content text NOT NULL,
postedAt date NOT NULL,
author_id bigint(20) NOT NULL,
FOREIGN KEY (author_id) REFERENCES User(id),
PRIMARY KEY (id)
);
-- !Downs
DROP TABLE Post;
Playは、このevolutionスクリプトをJamieのデータベースに適用します。
一方、RobinはUserテーブルの変更を必要とする機能に取り組むこととします。したがって、Robinも次の2.sql
evolutionスクリプトを作成します。
-- Update User
-- !Ups
ALTER TABLE User ADD age INT;
-- !Downs
ALTER TABLE User DROP age;
Robinは機能を完了し、(Gitを使用して)コミットします。ここで、Jamieは作業を続行する前にRobinの作業をマージする必要があるため、Jamieはgit pullを実行します。すると、マージで次のような競合が発生します。
Auto-merging db/evolutions/2.sql
CONFLICT (add/add): Merge conflict in db/evolutions/2.sql
Automatic merge failed; fix conflicts and then commit the result.
各開発者が2.sql
evolutionスクリプトを作成しました。そのため、Jamieはこのファイルの内容をマージする必要があります。
<<<<<<< HEAD
-- Add Post
-- !Ups
CREATE TABLE Post (
id bigint(20) NOT NULL AUTO_INCREMENT,
title varchar(255) NOT NULL,
content text NOT NULL,
postedAt date NOT NULL,
author_id bigint(20) NOT NULL,
FOREIGN KEY (author_id) REFERENCES User(id),
PRIMARY KEY (id)
);
-- !Downs
DROP TABLE Post;
=======
-- Update User
-- !Ups
ALTER TABLE User ADD age INT;
-- !Downs
ALTER TABLE User DROP age;
>>>>>>> devB
マージは非常に簡単に行えます。
-- Add Post and update User
-- !Ups
ALTER TABLE User ADD age INT;
CREATE TABLE Post (
id bigint(20) NOT NULL AUTO_INCREMENT,
title varchar(255) NOT NULL,
content text NOT NULL,
postedAt date NOT NULL,
author_id bigint(20) NOT NULL,
FOREIGN KEY (author_id) REFERENCES User(id),
PRIMARY KEY (id)
);
-- !Downs
ALTER TABLE User DROP age;
DROP TABLE Post;
このevolutionスクリプトは、Jamieがすでに適用済みの以前のリビジョン2とは異なる、新しいデータベースのリビジョン2を表しています。
そのため、Playはそれを検出し、Jamieに、すでに適用済みの古いリビジョン2を元に戻し、新しいリビジョン2スクリプトを適用することで、データベースを同期するように求めます。
§不整合な状態
evolutionsスクリプトで間違いを犯し、失敗することがあります。この場合、Playはデータベーススキーマを不整合な状態としてマークし、続行する前に手動で問題を解決するように求めます。
たとえば、このevolutionのUpsスクリプトにエラーがあるとします。
-- Add another column to User
-- !Ups
ALTER TABLE Userxxx ADD company varchar(255);
-- !Downs
ALTER TABLE User DROP company;
そのため、このevolutionを適用しようとすると失敗し、Playはデータベーススキーマを不整合としてマークします。
ここで、続行する前に、この不整合を修正する必要があります。そこで、修正済みのSQLコマンドを実行します。
ALTER TABLE User ADD company varchar(255);
…次に、ボタンをクリックして、この問題を手動で解決済みとしてマークします。
しかし、evolutionスクリプトにエラーがあるため、おそらく修正したいと思うでしょう。そこで、3.sql
スクリプトを変更します。
-- Add another column to User
-- !Ups
ALTER TABLE User ADD company varchar(255);
-- !Downs
ALTER TABLE User DROP company;
Playは、以前の3つを置き換えるこの新しいevolutionを検出し、適切なスクリプトを実行します。これで、すべてが修正され、作業を続行できます。
ただし、開発モードでは、開発データベースを破棄して最初からすべてのevolutionsを再適用する方が簡単な場合があります。
§トランザクショナルDDL
デフォルトでは、各evolutionスクリプトの各ステートメントは即座に実行されます。データベースがトランザクショナルDDLをサポートしている場合は、application.conf
でevolutions.autocommit=false
を設定してこの動作を変更し、すべてのステートメントを1つのトランザクションでのみ実行するようにできます。autocommitが無効な状態でevolutionスクリプトの適用が失敗した場合、トランザクション全体がロールバックされ、変更はまったく適用されません。そのため、データベースは「クリーン」な状態を維持し、不整合になることはありません。これにより、上記のように手動でデータベースを変更することなく、evolutionスクリプトでDDLの問題を簡単に修正できます。
§Evolutionのストレージと制限
Evolutionsは、データベース内のplay_evolutions
というテーブルに保存されます。Text列には、実際のevolutionスクリプトが格納されます。データベースには、テキスト列に64kbのサイズ制限がある可能性があります。64kbの制限を回避するには、play_evolutionsテーブル構造を手動で変更して列のタイプを変更するか、(推奨される方法として)64kb未満の複数のevolutionsスクリプトを作成することができます。
次へ: サーバーバックエンド
このドキュメントにエラーを見つけましたか?このページのソースコードはこちらにあります。ドキュメントガイドラインをお読みになった後、プルリクエストを自由に送信してください。質問や共有するアドバイスがありますか?コミュニティフォーラムにアクセスして、コミュニティと会話を始めましょう。