概述

对于映射,我们需要定义

  • 哪些实体应映射到索引
  • 对于每种实体类型,对象图的哪一部分包含在文档中
  • 哪些字符串属性实际上是 codes,不应进行分析
  • 我们希望哪些字符串属性同时包含 已分析和“原始” 字段,以便进行搜索和排序
  • 任何额外的 ElasticSearch 特定映射

@DocStore - 要映射的实体

我们对要映射到 ElasticSearch 索引的每个实体添加 @DocStore 注释。

// Store contact in ElasticSearch
@DocStore
@Entity
public class Contact {

默认情况下,@DocStore 表示

  • @OneToMany 和 @ManyToMany 不包含在内
  • @ManyToOne 和 @OneToOne 仅包含关联的 @Id 属性
  • 所有其他持久属性都包含在文档中

您可以通过指定要通过 doc 包含的属性来有效减少包含在索引中的属性。

示例:仅索引某些属性
@DocStore(doc="firstName, lastName, email")
@Entity
public class Contact {
...

我希望将要索引的属性减少到上述属性,这相对罕见。TODO:添加 @DocIgnore 和 @DocProperty(ignore=true) 支持。

@DocEmbedded - 嵌入式文档

在 @ManyToOne 和 @OneToMany 属性上,您可以使用 @DocEmbedded 指定应包含在要索引的文档中的属性。

示例:嵌入式 ManyToOne

将客户 ID、名称嵌入到联系人索引中。

@DocStore
@Entity
public class Contact {

  ...
  // denormalise including the customer id and name
  // into the 'contact' document
  @DocEmbedded(doc="id,name")
  @ManyToOne(optional=false)
  Customer customer;
示例:嵌入式 OneToMany

嵌入一些客户详细信息(客户 ID 和名称)。嵌入订单详细信息(作为 ElasticSearch “嵌套”属性,因为它是一个 @OneToMany)。

@DocStore
@Entity
public class Order {

  ...
  @DocEmbedded(doc="id,name")
  @ManyToOne(optional=false)
  Customer customer;

  @DocEmbedded
  @OneToMany(cascade = CascadeType.ALL, mappedBy = "order")
  List<OrderDetail> details;
示例:嵌入嵌套

嵌入更多客户详细信息,包括嵌套的 billingAddress 和 billingAddress.country。嵌入更多订单详细信息,包括嵌套的产品,其中包含 id、sku 和名称。

@DocStore
@Entity
public class Order {

  ...
  // embed some customer details including the billingAddress
  @DocEmbedded(doc = "id,name,status,billingAddress(*,country(*))")
  @ManyToOne(optional=false)
  Customer customer;

  @DocEmbedded(doc = "*,product(id,sku,name)")
  @OneToMany(cascade = CascadeType.ALL, mappedBy = "order")
  List<OrderDetail> details;

@DocCode - 作为“代码”的字符串

我们希望使用 @DocCode 映射一些字符串属性,以便不会分析属性值,而是将它们视为文本值/代码(不会由分析器小写/词干化等)。

Ebean 会自动将 UUIDEnum 和任何字符串 @Id 属性视为“代码”,并且您无需使用 @DocCode 对它们进行注释。

请注意,如果您将 @DocCode 放置在产品 sku 上,那么无论它嵌入在何处,它都被视为代码。因此,如果产品 sku 嵌入在订单索引中,它也将被视为那里的 @DocCode 属性。

示例

我们希望将产品 sku 值视为文本代码(不分析)。

@DocStore
@Entity
public class Product {

  // treat sku as a "code" (not analysed)
  @DocCode
  String sku;

  @DocSortable
  String name;

映射

@DocCode 属性被映射为未分析。

"properties" : {
  "sku": { "type": "string", "index": "not_analyzed" },
  ...

@DocSortable - 已分析和未分析

我们希望使用 @DocSortable 注释一些字符串属性,这样做会为该属性提供已分析和未分析/原始字段。我们可以将已分析字段用于文本搜索,我们可以将未分析/原始字段用于排序(和 ElasticSearch 聚合功能)。

请注意,如果您将 @DocSortable 放置在客户名称上,那么无论它嵌入在何处,它都被视为可排序。因此,如果客户名称嵌入在订单索引中,它也将被视为那里的 @DocSortable。

示例:客户

我们希望能够按客户名称排序。

@DocStore
@Entity
public class Customer {

  ...
  @DocSortable
  String name;
示例:产品

我们希望能够按产品名称排序(并且我们可以按产品 sku 排序,因为它是代码)。

@DocStore
@Entity
public class Product {

  @DocCode
  String sku;

  @DocSortable
  String name;

映射

@DocSortable 属性使用附加的“原始”未分析字段进行映射。

"properties" : {
  "name": { "type": "string", "fields": {
            "raw": { "type": "string", "index": "not_analyzed" } } },
  ...

查询使用 - 按排序

当您编写 Ebean 查询并指定按排序子句时,Ebean 会自动转换按排序子句以使用关联的“原始”字段(如果已定义)。

List<Product> products = server.find(Product.class)
  .setUseDocStore(true)
  .order().asc("name")
  .findList();
Elastic 查询
// name.raw used automatically for sort order
{"sort":[{"name.raw":{"order":"asc"}}],"query":{"match_all":{}}}

查询使用 - 术语表达式

“等于”转换为 Elastic “term”查询,对于 @DocSortable 属性,term 表达式将使用关联的“原始”字段。

类似地,“大于”、“小于”、“大于或等于”和“小于或等于”也转换为范围查询,在可用时也使用“原始”字段。

List<Product> products = server.find(Product.class)
  .setUseDocStore(true)
  .where().eq("name","Chair")
  .findList();
Elastic 查询
// name.raw used automatically for 'term' expression
{"query":{"filtered":{"filter":{"term":{"name.raw":"Chair"}}}}}

@DocProperty

@DocProperty 提供所有额外的映射选项,包括

  • store 默认值 false
  • boost 默认值 1
  • includeInAll 默认值 true
  • enabled 默认值 true
  • norms 默认值 true
  • docValues 默认值 true
  • nullValue
  • analyzer
  • searchAnalyzer
  • copyTo
  • index 选项 - DOCS、FREQS、POSITIONS、OFFSETS

它还提供标志,将 codesortable 设置为 @DocCode@DocSortable 的替代项。

@DocProperty 可以放在属性上,也可以放在 @DocStore mapping 属性上,此处的映射有效地覆盖任何现有的属性映射。

@DocStore(mapping = {
  @DocMapping(name = "description",
    options = @DocProperty(enabled = false)),
  @DocMapping(name = "notes",
    options = @DocProperty(boost = 1.5f, store = true))
})
@Entity
public class Content {

映射生成

为了有效地将 ElasticSearch 与相对结构化的 ORM 文档结合使用,我们需要使用适当的属性映射(类型、代码、可排序性等)创建 ElasticSearch 索引,这在某种程度上类似于 SQL 数据库的 DDL。

ebean.docstore.generateMapping=true

使用 ebean.docstore.generateMapping=true,Ebean 将为每个映射的 bean 类型(使用 @DocStore)生成一个映射文件。默认情况下,这些映射文件进入 src/main/resources,然后进入 elastic-mapping,这可以通过 DocStoreConfig pathToResources 和 mappingPath 进行配置。

预计在开发/测试期间使用此功能。

ebean.docstore.dropCreate=true

使用 ebean.docstore.dropCreate=true,Ebean 在启动时将删除所有映射的索引,并使用生成的映射重新创建它们。

预计在开发/测试期间使用此功能。

ebean.docstore.create=true

使用 ebean.docstore.create=true,Ebean 在启动时将检查哪些索引存在,并使用生成的映射创建任何缺失的索引。

预计在开发/测试期间使用此功能。

请注意,仅在 dropCreate 为 false 时才使用 create=true

示例映射

可在 src/main/resources/elastic-mapping 中找到示例应用程序的生成映射示例。

示例:product_v1.mapping.json
{
  "mappings" : {
    "product" : {
      "properties" : {
        "sku": { "type": "string", "fields": { "raw": { "type": "string", "index": "not_analyzed" } } },
        "name": { "type": "string", "fields": { "raw": { "type": "string", "index": "not_analyzed" } } },
        "whenCreated": { "type": "date" },
        "whenModified": { "type": "date" },
        "version": { "type": "long" }
      }
    }
  }
}
示例:customer_v1.mapping.json
{
  "mappings" : {
    "customer" : {
      "properties" : {
        "status": { "type": "string", "index": "not_analyzed" },
        "name": { "type": "string" },
        "smallNote": { "type": "string" },
        "anniversary": { "type": "date" },
        "billingAddress" : {
          "properties" : {
            "id": { "type": "long" },
            "line1": { "type": "string" },
            "line2": { "type": "string" },
            "city": { "type": "string" },
            "country" : {
              "properties" : {
                "code": { "type": "string", "index": "not_analyzed" },
                "name": { "type": "string" }
              }
            },
            "whenCreated": { "type": "date" },
            "whenModified": { "type": "date" },
            "version": { "type": "long" }
          }
        },
        "shippingAddress" : {
          "properties" : {
            "id": { "type": "long" },
            "line1": { "type": "string" },
            "line2": { "type": "string" },
            "city": { "type": "string" },
            "country" : {
              "properties" : {
                "code": { "type": "string", "index": "not_analyzed" },
                "name": { "type": "string" }
              }
            },
            "whenCreated": { "type": "date" },
            "whenModified": { "type": "date" },
            "version": { "type": "long" }
          }
        },
        "whenCreated": { "type": "date" },
        "whenModified": { "type": "date" },
        "version": { "type": "long" }
      }
    }
  }
}