ビデオ
概要
SQL2011では、AS OF SYSTEM TIME
やVERSIONS BETWEEN SYSTEM TIME
などの時間的拡張機能がSQLに導入されました。
EbeanはこのSQL拡張機能をサポートしています。このサポートは、SQL2011サポートが組み込まれているデータベース(Oracle、DB2、MS SQL Server 2016)と、組み込まれていないデータベース(Postgres、MySql)の2つの一般的なケースに分類されます。後者の場合、Ebeanは履歴テーブル/トリガー/ビューを生成してこの機能をサポートします。
Ebeanの現在のサポート
Oracle
トータル・リコール経由Postgres
トリガー/履歴テーブル経由MySql
トリガー/履歴テーブル経由H2
トリガー/履歴テーブル経由
履歴テーブル
Postgres、MySql、H2(および一般的にSQL2011サポートが組み込まれていないデータベース)の場合、Ebeanは履歴テーブルと関連するトリガーおよびビューのDDLを生成します。これは、Ebeanに固有のものではなく、以前から使用されている一般的な「テンポラルデータベース設計」アプローチです。たとえば、このアプローチはPostgres拡張機能として既に実装されています - arkhipov/temporal_tables。
一般に、履歴テーブルアプローチでは、以下のようになります。
- 「ベーステーブル」と同じ(または非常に類似した)構造を持つ「履歴」テーブルが作成されます。
- 「履歴」テーブルには、デフォルトでは制約がありません(主キー、外部キー、インデックスなし)。
- 「有効開始」と「有効終了」のタイムスタンプ範囲用に、ベーステーブルと履歴テーブルに2つの追加列が追加されます。Postgresにはタイムスタンプ範囲タイプがあるため、Postgresの実装では2つの separate 列よりもこちらが推奨されます。
- 履歴クエリを簡素化するために、ビューを使用して「ベーステーブル」と「履歴テーブル」を
union all
で結合します。 - 更新および削除時にトリガーを使用して、既存の行データを「ベーステーブル」から関連する「履歴テーブル」にコピーします。
- 「ベーステーブル」へのスキーマ変更(列の追加など)は、「履歴テーブル」へのミラーリングされた変更、および場合によっては関連するトリガーの更新につながります。
関連項目
- clarkdaveブログ投稿 - historical-records-with-postgresql
- Postgresql Wiki - SQL2011Temporal
- Postgres拡張機能 - arkhipov/temporal_tables
変更ログとの比較
トランザクション
@Historyを使用すると、変更はトランザクションとなり、関連する保証が適用されます。変更が見逃されたり失われたりすることはありません。
バイパスされない
@Historyを使用すると、データベースがすべての変更を処理します。他のフレームワーク(Ebean以外)またはストアドプロシージャまたは生のJDBCを使用してデータベースを更新する場合、これらの変更はすべてデータベーストリガーによってキャプチャされます。Ebeanは適切なDDLのクエリと生成を容易にしますが、変更のキャプチャはデータベース関数であり、Ebeanまたはアプリケーションコードに厳密に関連付けられているわけではありません。
クエリが容易
@Historyを使用すると、特定のタイムスタンプの「時点」でデータをクエリして表示し、特定のBean/行のバージョンと履歴の変更を表示するのが容易になります。これは変更ログでも可能ですが、より多くの作業が必要です(変更ログをElasticSearchなどにロードし、時点およびバージョンのタイプのデータを返すための適切なクエリを作成するなど)。
DBオーバーヘッド
変更ログの使用と比較した場合の@Historyの欠点は、ストレージ(履歴テーブルがストレージスペースを占有する)と応答時間への影響(更新と削除で履歴テーブルにデータがコピーされるようになるため)の点で、データベースに追加のコストが発生することです。
Oracle Total Recallのアプローチでは、REDOログからのデータのコピーをバックグラウンドでバッチ処理することで応答時間への影響を軽減します。そのため、おそらく従来のトリガーベースのアプローチよりも広く(より多くのベーステーブルで)使用できる可能性があります。
スキーマ変更
変更ログの使用と比較した場合の@Historyの欠点は、スキーマ変更(列の追加など)によって作業/複雑さが増す可能性があることです。変更は関連する履歴テーブルとトリガーにも反映される必要があるためです。EbeanのDBマイグレーションサポートは、履歴テーブルやトリガーなどへの適切な変更をすべて生成することで、これを軽減します。
@History
履歴サポートが必要なエンティティBeanに@History
アノテーションを追加します。PostgresとMySqlの場合、これはEbeanが基になるテーブルの履歴をサポートするためのDDL(トリガー、ビューなど)を生成することを意味します。Oracleの場合、@Historyを追加すると、テーブルにフラッシュバックアーカイブが既に割り当てられていることを意味します。
@HistoryExclude
履歴から除外する必要があるエンティティBeanプロパティに@HistoryExclude
アノテーションを追加します。これは、履歴値の保持に関連するストレージコストが比較的高い可能性のある大きなテキストまたはBLOB列で使用され、これらの列を除外することが望ましいと予想されます。
ManyToMany交差
ManyToMany関係の交差(ブリッジ)テーブルの場合、デフォルトでは交差テーブルにも履歴があります。つまり、エンティティBeanに@History
が配置されている場合、デフォルトでは@ManyToMany
プロパティの交差テーブルには履歴サポート(関連する履歴テーブル、ビュー、トリガー)があります。
交差テーブルから履歴を除外するには、@ManyToMany
プロパティに@HistoryExclude
を配置する必要があります。
時点クエリ
TimestampはQuery.asOf(Timestamp)
で使用でき、Ebeanは返されるオブジェクトグラフが指定されたタイムスタンプの時点の状態を表すようにクエリを生成します。より正確には、履歴のあるテーブルの場合、クエリは指定されたタイムスタンプの時点のテーブルを表す行を返し、クエリに含まれる他のテーブルの場合、返される行は「現在の値」を表します。
// asOf some time in the past like 1 hour ago, 1 week ago, 1 month ago etc
Timestamp asOf = ...;
Customer customer =
Customer.find.query()
.asOf(asOf)
.fetch("billingAddress")
.where().eq("name", "jim")
.findOne();
シナリオ:顧客と住所の両方に履歴がある
上記のクエリでは、顧客と住所の両方に履歴がある場合、顧客データと住所データの両方が指定されたタイムスタンプの「時点」で返されます。
シナリオ:顧客には履歴があるが、住所には履歴がない
上記のクエリでは、顧客には履歴があるが、住所には履歴がない場合、顧客データは指定されたタイムスタンプの「時点」で返されますが、住所データは「現在のデータ」を使用して返されます。
シナリオ:住所が遅延ロードされる
AS OF
タイムスタンプは、遅延ロードクエリを含むすべてのセカンダリクエリに伝播されます。つまり、AS OF
タイムスタンプは「元のクエリ」だけでなく、ロードされたオブジェクトグラフ全体に適用されます。住所が遅延ロードされ、住所に履歴がある場合、住所データも元のタイムスタンプの「時点」になります.
バージョン間
Query.findVersionsBetween()
は、特定のオブジェクトの一定期間にわたるバージョンのリストを返すために使用されます。返されるバージョンBeanには、前のバージョンとの「差分」と、有効開始タイムスタンプと有効終了タイムスタンプが含まれています。
Timestamp start = ...;
Timestamp end = ...;
List<Version<Customer>> customerVersions =
Customer.find.query()
.where()
.idEq(42)
.findVersionsBetween(start, end);
for (Version<Customer> customerVersion : customerVersions) {
Customer bean = customerVersion.getBean();
Map<String, ValuePair> diff = customerVersion.getDiff();
Timestamp effectiveStart = customerVersion.getStart();
Timestamp effectiveEnd = customerVersion.getEnd();
}
誰が、いつ
一般に、エンティティBeanには@WhoCreated
、@WhoModified
、@WhenCreated
、@WhenModified
プロパティが含まれている必要があります。
/**
* Common properties used by many entity beans.
*/
@MappedSuperclass
public class BaseModel {
@Id
Long id;
@Version
Long version;
@WhenCreated
Timestamp whenCreated;
@WhenModified
Timestamp whenModified;
@WhoCreated
String whoCreated;
@WhoModified
String whoModified;
...
@History
@Entity
@Table(name="customer")
public class Customer extends BaseModel {
@Size(max = 100)
String name;
...
Postgres
Postgresには既存のarkhipov/temporal_tables拡張機能があり、Postgres/Ebeanユーザーはこの拡張機能を使用できます。デフォルトでは、Ebeanはこの拡張機能を使用せず、同様のトリガーを生成します。arkhipov/temporal_tablesを使用するかどうかは、DDLの生成によって異なります。最終的には、Ebeanがarkhipov/temporal_tablesのDDL生成をサポートすることが望ましいですが、それはまだEbeanに組み込まれていません。
Postgresの履歴サポートのもう1つの注目すべき点は、タイムスタンプ範囲タイプを利用できることです。2つの separate タイムスタンプ列を使用する場合と比較して、インデックスのパフォーマンスに関して利点があると期待されます。
MySql
MySqlでは、本番データベースにDDL変更を適用する必要がある場合に懸念事項があります。スキーマ変更(列の追加など)によってトリガーを変更する必要がある場合、MySqlには「トリガーの作成または置換」がなく、代わりにトリガーを削除して作成するためにテーブルロックを保持する必要があります。本番データベースでテーブルロックを保持すると、問題が発生し、スキーマ変更が制限される可能性があります。
Oracle
Robの見解:Oracle Total Recallには、明示的なトリガー/履歴テーブルアプローチよりも優れた利点があります。主な利点は次のとおりです。
- パフォーマンス:フォアグラウンドコストが低い - 履歴テーブルの作成はバックグラウンドで行われ、フォアグラウンドの応答時間へのオーバーヘッド/影響がはるかに少なくなります。
- パフォーマンス:バッチ処理 - 履歴テーブルの作成はバッチ処理できるため、多数の小さな更新のオーバーヘッド/影響が軽減されます。
- 管理:DDLの削減 - テーブルの変更(列の追加など)は自動的に処理されるため、トリガー/ビュー/履歴テーブルの保守管理コストが削減されます。