ドキュメント

§データベースエボリューションの管理

リレーショナルデータベースを使用する場合、データベーススキーマの進化を追跡および整理する方法が必要です。通常、データベーススキーマの変更を追跡するためのより洗練された方法が必要な状況がいくつかあります。

§エボリューションの有効化

依存関係リストにevolutionsjdbcを追加します。たとえば、build.sbtでは次のようになります。

libraryDependencies ++= Seq(evolutions, jdbc)

§コンパイル時DIを使用したエボリューションの実行

コンパイル時依存性注入を使用している場合、EvolutionsComponentsトレイトをケーキにミックスインして、インスタンス化されたときにエボリューションを実行するApplicationEvolutionsにアクセスする必要があります。EvolutionsComponentsには、dbApiが定義されている必要があります。これは、DBComponentsHikariCPComponentsをミックスインすることで取得できます。applicationEvolutionsEvolutionsComponentsによって提供される遅延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つの部分が含まれています。

たとえば、基本的なアプリケーションをブートストラップするこの最初の進化スクリプトを見てください。

-- 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です。次の構成オプションがサポートされています。

たとえば、すべてのエボリューションに対して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.confevolutions.autocommit=falseを設定してこの動作を変更し、すべてのステートメントを1つのトランザクションでのみ実行するようにできます。autocommitが無効な状態でevolutionスクリプトの適用が失敗した場合、トランザクション全体がロールバックされ、変更はまったく適用されません。そのため、データベースは「クリーン」な状態を維持し、不整合になることはありません。これにより、上記のように手動でデータベースを変更することなく、evolutionスクリプトでDDLの問題を簡単に修正できます。

§Evolutionのストレージと制限

Evolutionsは、データベース内のplay_evolutionsというテーブルに保存されます。Text列には、実際のevolutionスクリプトが格納されます。データベースには、テキスト列に64kbのサイズ制限がある可能性があります。64kbの制限を回避するには、play_evolutionsテーブル構造を手動で変更して列のタイプを変更するか、(推奨される方法として)64kb未満の複数のevolutionsスクリプトを作成することができます。

次へ: サーバーバックエンド


このドキュメントにエラーを見つけましたか?このページのソースコードはこちらにあります。ドキュメントガイドラインをお読みになった後、プルリクエストを自由に送信してください。質問や共有するアドバイスがありますか?コミュニティフォーラムにアクセスして、コミュニティと会話を始めましょう。