提交后

当事务成功提交直接通过 @DocStore 映射到 ElasticSearch 的实体 Bean 或通过成为嵌入式文档的一部分(非规范化)间接映射到 ElasticSearch 的实体 Bean 将发生需要传播到 ElasticSearch 的更改。

这些实体 Bean 的处理发生在后台线程中,以便不影响事务的正常响应时间。

更改基于其 DocStoreMode 传播

  • UPDATE - 通过其批量 API 将更改发送到 ElasticSearch。
  • 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 文档属性),Ebean 知道在更新实体 Bean 时需要检查/更新的 嵌套路径

例如,嵌入在订单和联系人中的客户

例如,让我们索引客户,但还将客户作为嵌入式文档包含在订单索引和联系人索引中。

已索引的客户
@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 启动时,它使用映射,读取 @DocEmbedded doc 属性,并确定嵌套文档结构。然后,为每个 嵌套路径 注册一个侦听器。在上面的示例中,向客户注册了 2 个侦听器,其中一个将更新联系人(如果客户姓名更改),另一个将更新订单(如果姓名、状态或账单地址更改)。

更改客户姓名

如果我们找到客户 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"}}}
  • 第一个条目更新客户索引
  • 第二个和第三个更新订单 5 和订单 2(Roberto 的相关订单)
  • 第四个更新联系人 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 扫描查询以查找所有需要更新的相关条目。

例如,嵌入式国家/地区

在下面的示例中,客户索引包含账单和送货地址的嵌入式文档,而这反过来又嵌入了国家/地区。在此示例中,"billingAddress.country.code""shippingAddress.country.code"嵌套路径,Ebean 需要检查这些路径以查看当国家/地区名称更改时需要更新哪些客户索引。

在下面的示例中,国家/地区嵌入在客户索引中,既在账单地址中又在送货地址中。当我们更新国家/地区时,还需要更新其账单或送货地址中包含该国家/地区的任何客户文档。

@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 嵌入在订单索引中,因此我们还将找到具有此嵌入式国家/地区的订单。

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