コミット後処理

トランザクションが正常にコミットされると、@DocStore を介して直接、または埋め込みドキュメント(非正規化)の一部として間接的に ElasticSearch にマッピングされているエンティティ Bean の変更が、ElasticSearch に伝播される必要があります。

これらのエンティティ Bean の処理は、トランザクションの通常の応答時間に影響を与えないように、バックグラウンドスレッドで行われます。

変更は、それらの DocStoreMode に基づいて伝播されます。

  • UPDATE - 変更は、ElasticSearch のバルク API を介して送信されます。
  • QUEUE - 変更は、後で処理するためにキューにプッシュされます。
  • IGNORE - 変更は、アプリケーションが必要に応じて変更を見つけて伝播することを前提として、Ebean によって無視されます。

トランザクション DocStoreMode.IGNORE

トランザクションは DocStoreMode.IGNORE に設定でき、その場合、Ebean はそのトランザクション内のすべての変更を無視します。これは、大規模なバッチ処理で使用することを意図しており、Ebean が変更の通常の処理を実行せず、代わりにアプリケーションが後で ElasticSearch に伝播する変更を検索することを最適と判断した場合に使用されます。

Transaction transaction = server.beginTransaction();
transaction.setDocStoreMode(DocStoreMode.IGNORE);
try {

  // perform lots of changes and we don't want
  // Ebean to propagate those (as it would normall)
  transaction.commit();
} finally {
  transaction.end();
}

// typically application code later finds and
// updates indexes as necessary
// for example:

Query<Product> query = server.find(Product.class)
  .where()
    .ge("whenModified", new Timestamp(since))
    .query();

// update products modified after a given dateTime
server.docStore().indexByQuery(query, 1000);

挿入

エンティティ Bean が挿入されると、DocStoreUpdate に追加され、DocStoreUpdateProcessor に送信されます。

これは、ElasticSearch のバルク更新における index エントリに変換されます。

例:国の挿入

Country country = new Country("SA","South Africa");
country.save();

バルク API

{"index":{"_id":"SA","_type":"country","_index":"country"}}
{"name":"South Africa"}

削除

エンティティ Bean が削除されると、DocStoreUpdate に追加され、DocStoreUpdateProcessor に送信されます。

これは、ElasticSearch のバルク更新における delete エントリに変換されます。

例:国の削除

Ebean.delete(Country.class, "SA");

バルク API

{"delete":{"_id":"SA","_type":"country","_index":"country"}}

更新

更新の処理は、挿入や削除よりも複雑です。更新では、メインの @DocStore インデックスを更新するだけでなく、影響を受けた/更新されたプロパティが埋め込みドキュメントの一部として含まれているインデックス(通常は @DocEmbedded を介して)も更新する必要があるためです。

例:国の更新

Country sa = fetchSaFromDocStore();
sa.setName("Sud Africa");
sa.save();

バルク API

{"update":{"_id":"SA","_type":"country","_index":"country"}}
{"doc":{"name":"Sud Africa"}}

埋め込みドキュメント

エンティティ Bean を更新する場合は、エンティティ Bean が埋め込まれているインデックスも更新する必要があります。

@DocEmbedded は埋め込みドキュメント(非正規化)を表します。エンティティ Bean が更新されると、Ebean は関連する埋め込みドキュメントも更新しようとします。

マッピング(@DocEmbedded の doc 属性)に基づいて、Ebean はエンティティ Bean が更新されたときに確認/更新する必要がある ネストされたパス を認識します。

例:注文と連絡先に埋め込まれた顧客

たとえば、顧客をインデックスに登録し、顧客を Order インデックスと Contacts インデックス内の埋め込みドキュメントとして含めることを考えます。

インデックス登録された顧客
@DocStore
@Entity
public class Customer ...
連絡先に埋め込まれた顧客
@DocStore
@Entity
public class Contact extends BasicDomain {

  ...
  @ManyToOne(optional = false)
  @DocEmbedded(doc = "id,name")
  Customer customer;
注文に埋め込まれた顧客
@DocStore
@Entity
@Table(name = "orders")
public class Order extends BasicDomain {

  ...
  @NotNull @ManyToOne
  @DocEmbedded(doc = "id,status,name,billingAddress(*,country(*)")
  Customer customer;

顧客の名前が更新されると、Ebean は次の処理を行う必要があります。

  • 顧客インデックスの更新
  • 関連する連絡先の更新(ネストされたパスの更新に基づく)
  • 関連する注文の更新(ネストされたパスの更新に基づく)

Ebean の起動時に、マッピングを使用して @DocEmbeddeddoc 属性を読み取り、ネストされたドキュメント構造を決定します。その後、各 ネストされたパス に対してリスナーを登録します。上記の例では、顧客に 2 つのリスナーが登録されており、1 つは連絡先を更新し(顧客の名前が変更された場合)、もう 1 つは注文を更新します(名前、ステータス、請求先住所が変更された場合)。

顧客名の変更

顧客 2 を見つけて名前を「Roberto」に変更すると、次のようになります。

バルク API
{"update":{"_id":"2","_type":"customer","_index":"customer"}}
{"doc":{"name":"Roberto","whenModified":1459206556280,"version":2}}
{"update":{"_id":"5","_type":"order","_index":"order"}}
{"doc":{"customer":{"id":2,"status":"NEW","name":"Roberto","billingAddress":null}}}
{"update":{"_id":"2","_type":"order","_index":"order"}}
{"doc":{"customer":{"id":2,"status":"NEW","name":"Roberto","billingAddress":null}}}
{"update":{"_id":"4","_type":"contact","_index":"contact"}}
{"doc":{"customer":{"id":2,"name":"Roberto"}}}
  • 1 番目のエントリは顧客インデックスを更新します。
  • 2 番目と 3 番目は、Order 5 と Order 2(Roberto の関連する注文)を更新します。
  • 4 番目は、Contact 4(Roberto の関連する連絡先)を更新します。

ネストされたパス

ネストされたパス について、Ebean は ElasticSearch スキャン クエリを実行して、更新する必要があるインデックス内のエントリを見つけます。

関連する注文の検索
{"fields":["customer.id","id"],"query":{"filtered":{
  "filter":{
    "terms":{"customer.id":[2]}
  }
}}}
関連する連絡先の検索
{"fields":["customer.id","id"],"query":{"filtered":{
  "filter":{
    "terms":{"customer.id":[2]}
  }
}}}

データベースに対して ORM クエリを実行して、バルク API コールに含める JSON を構築しますが、上記のように、ElasticSearch スキャン クエリを実行して、更新するすべての関連エントリを見つけます。

例:埋め込み国

以下の例では、Customer インデックスには、請求先住所と配送先の住所の両方の埋め込みドキュメントが含まれており、これには国も埋め込まれています。この例では、"billingAddress.country.code""shippingAddress.country.code" は、国の名前が変更されたときに更新する必要がある Customer インデックスを Ebean が確認する必要がある ネストされたパス です。

以下の例では、国は請求先住所と配送先の住所の両方で Customer インデックスに埋め込まれています。国を更新する場合は、請求先住所または配送先にその国が含まれている Customer ドキュメントも更新する必要があります。

@DocStore
@Entity
public class Customer extends BasicDomain {
  ...
  @DocEmbedded(doc = "*,country(*)")
  @ManyToOne(cascade = CascadeType.ALL)
  Address billingAddress;

  @DocEmbedded(doc = "*,country(*)")
  @ManyToOne(cascade = CascadeType.ALL)
  Address shippingAddress;

例 ネストされたパス - billingAddress.country.code

Ebean は、埋め込みドキュメントの変更により更新する必要があるドキュメントを見つけるために、ネストされたパス を使用して ElasticSearch に対してスキャン クエリを実行します。

billingAddress.country.code = SA の顧客の検索
{"fields":["billingAddress.id","id"],"query":{
    "filtered":{"filter":
      {"terms":{"billingAddress.country.code":["SA"]}
    }}
}}
shippingAddress.country.code = SA の顧客の検索
{"fields":["shippingAddress.id","id"],"query":{
    "filtered":{"filter":
      {"terms":{"billingAddress.country.code":["SA"]}
    }}
}}
customer.billingAddress.country.code = SA の注文の検索

国は customer.billingAddress を介して Order インデックスにも埋め込まれているため、この埋め込み国を持つ注文も検索します。

{"fields":["customer.id","id"],"query":{"filtered":{
  "filter":{
    "terms":{"customer.billingAddress.country.code":["SA"]}
  }
}}}