概要
playframework1に当初から内包されているDBスキーマを管理する機能の名前。普段、DB周りはJPAに任せてしまうことが多いのですが、先日初めて evolutionを使ってみたのでそのとき得た知見をまとめておこうと思います。
何ができるのか
JPA(Hibernate)は自動でデータベースのスキーマ更新を行うことができます。小規模開発ならJPAの自動更新(インデックスの削除など不可なケースもある)に任せてもいいのですが、ある程度のプロジェクトであればスキーマの変更を追跡、管理する必要が出てきます。
スキーマの変更をメンバー全員に周知したり、本番サーバのスキーマ変更を明示的に行ったり、いくつかの開発環境DBを同期させたり、などの場面で役立ちます。
準備
- Evolution機能を利用する際はまず、ディレクトリ
db/evolutionsを作成する。 - そのディレクトリの中に、1.sql, 2.sql, とDDLを作成する。
スキーマ変更を行うたびに新しい番号でDDLを記述したファイルを配置する。
SQLスクリプトファイル
db/evolutionsに配置するSQLスクリプトは、UpsとDownsの2つのパートから構成される。- Upsは変更するためのスクリプト。(例えば、
create table XXX...) - Downsは元に戻すためのスクリプト。(例えば、
drop table XXX...)
マルチDB対応
マルチDBにも対応しています。マルチDBでは各DBに識別子を設定しますが、その識別子をスクリプトファイルの先頭に記述します。
1.sql, 2.sql, ... となるところが、DB-1.1.sql, DB-1.2.sql, ...,DB-2.1.sql, DB-2.2.sql,... といった形になります。
設定
application.conf による動作の制御方法
evolutions.enabled=false
Evolutions機能を利用しないときに false を設定する。
modules.evolutions.enabled=false
Evolutionsモジュールを利用しないときに設定する。
dbname.evolutions.enabled=false
マルチDBでDB指定してEvolutionsモジュールを利用しないときに設定する。
evolutions.autocommit=false
autocommitをしないときに指定する。
evolution.PLAY_EVOLUTIONS.textType=varchar(32768)
Evolutions機能で作成する管理テーブル play_evolutions の apply_script , revert_script , last_problem 列の型を指定する。未指定の時、oracleなら clob型、他は text型 となる。
デフォルトの hd2b を使う場合は varchar(32768) を指定する必要があった。
db/evolutions ディレクトリの存在でモジュールはONとなるので、上記設定で個別にOFFにしていく感じになります。
動作
DEVモード
DEVモードの時はリクエストが発生するたびにスキーマ状態の確認が割り込みます。そこで未適用のスクリプトが見つかった場合は適用を要求するページが表示されます。(例外オブジェクトの getMoreHTML にて適用するための <form/> を含む html を返すことで適用要求ページを実現している)
詳しくは調べていないのですが、UnexpectedException にラップされて <form /> が表示されない事象が発生したので以下のように views/errors/500.html を修正して対応しました。

<body>
#{if play.mode.name() == 'DEV'}
#{if exception?.cause instanceof play.exceptions.PlayException }
#{500 exception.cause /}
#{/if}#{else}
#{500 exception /}
#{/else}
#{/if}
#{else}
<h1>Oops, an error occurred</h1>※ db=mem で中身が空の場合は問い合わせなく勝手にスクリプトが適用される。
PRODモード
本番モードなら最初の起動時にスキーマ状態の検査が行われます。未適用のスクリプトが見つかった場合はエラーとなって起動中断となります。
アプリケーションが要求するDBスキーマとなっていないと起動しないようになっています。
コマンドライン
コマンドラインよりスクリプトの適用を制御します。
$ play evolutions
未適用のスクリプトを表示する。
$ play evolutions:apply
未適用のスクリプトを適用する。
$ play evolutions:markApplied
未適用と認識されているスクリプトを Evolutions機能を経由しないで適用したときに使用する。(未適用スクリプトを強制的に適用済ステータスにする)
$ play evolutions:resolve
スクリプトの実行中にエラーが発生すると問題が発生したスクリプトとして記録され、以降の適用が保留となる。そのときこのコマンドで問題が解決済とする。(問題発生ステータスをクリアする)
※ play evolutions は play ev と省略することが可能です。
Tips
DDL作成
自分で一からSQLスクリプトファイルを書くのは大変なのでJPA(Hibernate)を利用すると楽ができます。
jpa.ddl=update
jpa.ddl=create
この設定にしておけば起動時にDDL変更を自動で行ってくれる。
jpa.debugSQL=true
hibernate.show_sql=true
DDL変更のSQLがログに出力されるのでこれをスクリプトファイルへ転記すればよい。
戻しについてはJPAからの出力は期待できないので自分でスクリプトを用意する必要があります。
module_key カラムについて
管理テーブル play_evolutions には、module_key というカラムが有、application.name の値が格納されていて、抽出条件にもなっています。
複数の playframeworkアプリが同一DBに対して Evolutionsを利用することを想定しているようです。
なので、application.name の扱いには注意しましょう。途中で変更したり、開発時だけ名前を変えていたりすると、予期しない挙動を招く可能性があります。
途中からEvolutionを利用するとき
既に開発が進んでいるプロジェクトで Evolutionsを利用するときの手順を示します。
- アプリケーション停止する。
db/evolutionsディレクトリを作成する。- 「
1.sql」 を設置(現時点のDBのDDLを取得して作成) - 「
play ev」コマンドを実行する。(管理テーブルが作成されて、1.sqlが未適用として表示される) - 「
play ev:markedApplied」コマンドを実行して適用済とマークする。 - アプリケーション起動する。
以上になります。以降のDDL変更は、#.sql へ記述します。
h2dbで現時点のスキーマを出力する方法
sql> script nodata to 'C:\work\ddl.sql
データやスキーマをSQLとして書き出すことができるコマンド。今回はデータなしなので nodata を指定。
参考)https://h2database.com/html/commands.html#script
本番リリースするとき
新しいバージョンのアプリケーションに入れ替えるときは、停止中にDDL適用を行います。
- 「
play ev:apply」コマンドを実行し、未適用のスクリプトを適用する。 - apply中にエラーが出てしまったらステータスがエラーになってしまうので、「
play ev:resolve」でエラー状態を解消する。
問題を解決して再びスクリプト適用を行う。
evolution.PLAY_EVOLUTIONS.textTypeの必要性
h2dbでは下図のような管理テーブル play_evolutions が作られます。

ここで apply_script や revert_script 列のデータ型が clob型となっている点に注目してください。Evolutionsではこれらを resultSetからgetString(n) で取得しているのですが、Clob型のtoStringではテキストの中身ではなく `java.sql.Clob@123′ のようなオブジェクトID文字列になってしまいます。
そのオブジェクトID文字列とスクリプトファイルの中身を比較することになるために常にNGとなってしまうのです。
そこでDBデータ型を明示的に指定することでこの問題を回避することができるのです。
evolution.PLAY_EVOLUTIONS.textType=varchar(32768)
※ この設定だけ evolutionが単数形なので注意。
org.h2.jdbc.JdbcSQLSyntaxErrorException: 列名 “MODULE_KEY” が重複しています
想像ですが。昔の Evolutionsでは module_key による管理がなく、ある時から管理できるようになった。そのため昔に作られた管理テーブル play_evolutions に自動でカラムを追加してくれるマイグレーション機能が存在するみたいです。
で、module_keyが存在しているのにこのマイグレーション機能が誤作動してしまうと題名のようなエラーが発生することがあります。
具体的例としては、h2dbの DATABASE_TO_UPPER がtrueになっていると大文字でテーブルやカラムが作成されてしまって存在チェックで誤検知が発生してしまうことになります。
play-1.5.3 + h2db@1.4.196 では小文字で play_evolutions が作成play-1.7.2 + h2db@1.4.200 では大文字で作成
組み合わせでデフォルト動作が異なるので JDBC_URL で明示的に指定した方が良いです。