视频

原因

概述:Ebean 具有 ElasticSearch 集成的原因

映射简介

使用 @DocCode、@DocSortable 和 @DocEmbedded 进行映射的简介

测试

有关如何配置测试的信息,请转到 docs / testing / elasticsearch

为什么集成?

对于使用 Ebean ORM 的应用程序而言,ElasticSearch 集成有许多不同的原因。

ElasticSearch 价值

  • 提供“全文搜索”
  • 提供类似于 DB 物化视图的内容
  • 扩展读取访问
  • 读取访问性能(文档存储)
  • 修复 L2 缓存问题
  • 提供 L3 缓存

提供“全文搜索”

许多现代应用程序提供一个搜索输入字段,以“Google 搜索”的方式进行操作。例如,搜索包括姓名和地址(街道、城市、国家等)在内的客户。

ElasticSearch 使用分析器和倒排索引,使“文本搜索”快速而高效。OLTP 数据库通常通过 BTree 和 BitMap 索引提供非常不同的索引,这些索引面向 OLTP 的需求(短事务、自平衡等)。

提供类似于 DB 物化视图的内容

许多数据库提供物化视图,这些视图通常是派生/计算视图,用于提供数据的更快视图。物化视图通常定期刷新,并且由应用程序使用,知道派生/计算数据仅定期刷新,并且可能过时。例如,产品定价可能每晚午夜刷新一次。

ElasticSearch 索引可以以类似于 DB 物化视图的方式查看。它们表示数据的物化(面向文档)视图,可以随着每次提交或以更定期批处理方式更新,几乎实时更新。

扩展读取访问

ElasticSearch 在架构上内置了水平扩展,与扩展更传统的 OLTP 数据库相比,这可以用来提供一种更简单/更便宜的方式来扩展读取访问。请注意,访问不等于大多数 OLTP 数据库提供的访问,因为它不提供相同的事务读取一致性保证,并且“接近”实时(通常延迟一秒等) - 但是,鉴于读取可扩展性得到改善,这些限制通常是可以接受的。

读取访问性能(非规范化文档存储)

ElasticSearch 还可以被视为文档存储,通常存储的“文档”是非规范化的,并且包含派生和连接的信息。这意味着在 elastic search 中检索单个文档可以与涉及许多连接的 SQL 查询竞争。例如,订单文档可能是非规范化的,否则在 SQL 查询中连接订单、订单详细信息、客户和产品表。

非规范化数据的传统缺点仍然存在,但是当您的 OLTP 数据库仍然是主数据存储,而 ElasticSearch 是辅助存储时,存储“非规范化文档”是很好管理的。

修复 L2 缓存问题

ElasticSearch 可用作L2 查询缓存的有效替代,该缓存针对经历大量插入、更新或删除更改的类型。

实质上,如果应用程序依赖于在经常更改的 bean 类型(插入、更新或删除)上的L2 查询缓存,那么由于 L2 查询缓存利用率低效(因为它经常失效),这将导致数据库负载。

ElasticSearch 提供了许多“精确值/术语”谓词,这些谓词很好地映射到“关系”谓词,并且许多关系/orm 查询可以在 ElasticSearch 上合理执行。如果需要针对经常更新的表扩展“查找多个”查询,那么 ElasticSearch 可以提供一个很好的解决方案,但需要注意的是 ElasticSearch 索引可能略有过期。

提供 L3 缓存

对于 Ebean,我们有术语L3 缓存,这意味着L2 缓存的远程部分。也就是说,L2 缓存有一个“近缓存”和“远程缓存”,这里的“L3 缓存”表示 L2 缓存的远程部分。

Ebean 可以将 ElasticSearch 用作 L3 缓存(而不是更传统的 Hazelcast/Infinispan/等分布式缓存)。如果本地 L2 缓存中出现未命中,Ebean 可以访问 ElasticSearch 而不是数据库,其优点是 ElasticSearch 已知拥有所有数据并以非规范化形式拥有这些数据(因此,针对 ElasticSearch 的一次命中可能比传统的数据库命中填充更多图形)。

ORM 图形作为 JSON 文档

ORM 图形自然映射到 JSON 文档。Ebean 还内置了编组/解组到 JSON 的支持,以及有效执行大型查询。这使得执行特定类型的所有对象的大型查询,将这些对象图形转换为 JSON 并发送到 ElasticSearch 变得完全自然且合理。

需要注意的是,ORM 图形通常结构良好(如果你在 DB 中存储 JSON,则为半结构化,而 Postgres 和 Oracle 对此提供了良好的支持)。在将 ORM 图形映射到 ElasticSearch 文档时,我们通常需要考虑映射,例如识别“代码”(不应分析)的属性,以及识别我们希望以分析和原始形式存在的属性(以支持查询的 order by 子句)。

自动同步

核心集成功能是提供良好的机制来同步对 ElasticSearch 的更改。也就是说,当 Ebean 处理对数据库的更改时,它会自动知道需要对索引进行哪些更新,并提供可供你控制如何以及何时发生这些更新的功能。

Ebean 根据映射确定需要哪些索引更新事件,你可以控制是否将这些更新立即在后台发送到 ElasticSearch,还是排队以供稍后处理或忽略(当你希望完全控制索引时)。

查询

你始终可以在不使用 Ebean 的情况下直接查询 ElasticSearch,这将非常适合某些用例。此外,Ebean 为执行查询提供了良好的支持,并具有许多好处。

自动使用“原始”字段

Ebean 将自动转换表达式以对排序(order by)子句和术语表达式使用关联的“原始”字段。也就是说,Ebean 通过映射知道哪些属性具有关联的未分析(原始)字段,并在需要时(order by 和术语表达式)使用这些字段。

持久性上下文

当我们希望从 ElasticSearch 中获取数据,然后将其持久化到我们的 OLTP DB 时使用。当对象图形从 JSON 文档构建时,持久性上下文用于对 bean 进行去重,从而为我们提供一致的图形(这通常是我们希望用于持久化的图形)。持久性上下文还在查询联接(将同一 bean 的多个 JSON 源合并在一起)中发挥作用。

加载上下文 - 延迟加载和查询联接

与 ORM 查询类似,我们可以使用加载上下文来提供高效的批量延迟加载和查询联接。从 ElasticSearch 构建的对象图可以调用延迟加载(到 L2、其他索引,当 bean 类型映射到 Elastic 索引时,回退到 OLTP DB)。加载上下文还提供“查询联接”,以便我们可以通过联接到其他索引来构建结果。例如,从客户索引中获取并联接联系人索引(在 ElasticSearch 术语中,这将被描述为“应用程序端联接”。我们可以通过批量加载高效地执行此操作,就像我们在传统的 ORM 查询联接中所做的那样(以避免 N + 1)。

查询 bean - 类型安全查询构建

我们可以使用查询 bean 以类型安全的方式构建查询,这对于长期维护非常有利,并且在编译时可以防止模式和类型更改。

命中 DB 还是 Elastic?

编写 ORM 查询然后轻松地将其更改为命中 ElasticSearch 的能力使应用程序开发人员能够延迟决策,并且在命中 DB 或 Elastic 之间相对轻松地进行更改。这不是一个“完美的”交换,需要考虑谓词的不区分大小写和读取一致性(并且不支持子查询),但对于很多情况来说,轻松地在两者之间进行交换的能力使开发人员能够继续构建,同时知道他们稍后可以重新审视(并且更改实现以命中 Elastic,而无需更改持久性上下文、延迟加载、保存回 DB 等方面的语义)。

也就是说,这为水平扩展读取提供了低障碍路径。

useDocStore(true)

在 Ebean 查询中,您只需要设置 useDocStore(true),然后查询将在 ElasticSearch 上执行。无法转换为 ElasticSearch 的唯一查询表达式是子查询表达式 - 否则所有表达式都可以转换为 ElasticSearch 表达式。

示例:查找分页列表
PagedList<Product> products = server.find(Product.class)
  .setUseDocStore(true) // hit Elastic index
  .where().startsWith("sku", "C00")
  .setMaxRows(20)
  .findPagedList();
示例:按 ID 查找
Product product = server.find(Product.class)
  .setUseDocStore(true) // hit Elastic index
  .setId(1)
  .findOne();

映射关系表达式

大多数“关系/精确值”表达式直接映射到 ElasticSearch 表达式,但有一些例外。

  • 请注意,在 ElasticSearch 中,Like、StartsWith、EndsWith 和 Contains 实际上都不区分大小写。
  • 子查询 IN - 目前不支持/不转换
  • 子查询 EXISTS - 目前不支持/未翻译

部分对象 - select() 和 fetch()

就像 ORM 查询一样,您可以优化 ElasticSearch 查询,仅获取您需要的文档部分。您可以使用常规的 Ebean 查询 select() 和 fetch() 来定义要取回的文档部分,Ebean 会将其应用于 ElasticSearch 查询。

List<Customer> customers =
  server.find(Customer.class)
    .useDocStore(true)
    // only fetch id, status and name from index
    // ... as a performance optimisation
    .select("status, name")
    .where().istartWith("name", "Rob")
    .findList();

查询联接

就像 ORM 查询一样,您可以“查询联接”多个索引。

// fetch from the customer index
// ... "join" the contacts index

List<Customer> customers = server.find(Customer.class)
  .setUseDocStore(true)
  // fetch related contacts from the contacts index
  .fetch("contacts", new FetchConfig().query())
  .findList();
// fetch from the order index
// ... "join" the customer index
//
// internally this merges the customer details
// ... from the order index with the customer details
// ... from the customer index

List<Order> orders = server.find(Order.class)
  .setUseDocStore(true)
  // say the order index only embeds customer(id,name)
  // so we additionally fetch (and merge) all customer
  // details including the billingAddress and shippingAddress
  // from the customer index
  .fetch("customer", new FetchConfig().query())
  .findList();

对它的支持可以改变有关对特定索引使用多少非规范化/嵌入式文档的决策。例如,在“订单”索引中,您需要决定将多少客户信息包含/嵌入到订单中。您可以嵌入客户 (id、名称) 或客户 (id、名称、账单地址 (*) ) 等。轻松联接“客户索引”(以获取任何必需的客户详细信息)的能力意味着您可以选择将更少的客户信息嵌入到订单文档中。

查询 - 全文

Ebean 也公开了全文查询表达式。这些仅用于“文档存储”查询,不适用于转到 OLTP 数据库的常规 ORM 查询。

您可以使用 text() 替代 where(),以向查询添加“全文”搜索表达式,如 match、multi-match、common terms、text simple query 和 text query。这些直接映射到相应的 ElasticSearch 全文表达式。

请注意,当您添加“文本表达式”时,查询会自动设置为 useDocStore(true)。

PagedList<Customer> customers = server.find(Customer.class)
  .text()
    .match("name", "Rob")
    .findPagedList();
PagedList<Customer> customers = server.find(Customer.class)
  .text()
    .match("name", "Rob")
    .match("notes", "kung fu")
    .findPagedList();
PagedList<Customer> customers = server.find(Customer.class)
  .text()
  .must()
    .match("customer.name", "Rob")
    .eq("status", Order.Status.COMPLETE)
    .findPagedList();
MultiMatch match = MultiMatch.fields("name", "notes").boost(2).opAnd();

PagedList<Customer> customers = server.find(Customer.class)
  .text()
    .multiMatch("Rob", match)
    .findPagedList();

当前超出范围

以下功能当前被认为超出范围。

ElasticSearch 父/子映射

ElasticSearch 提供父/子映射作为嵌套/嵌入/非规范化方法的替代方案。在此阶段,对它的支持超出范围,而嵌套/非规范化方法是首选。

请注意,Ebean 可以使用“查询联接”将多个索引联接成一个结果。