概要

マッピングには、以下の定義が必要です。

  • どのエンティティをインデックスにマッピングするか
  • 各エンティティ型について、オブジェクトグラフのどの部分をドキュメントに含めるか
  • どの文字列プロパティが実際にはcodesであり、分析すべきでないか
  • どの文字列プロパティに対して検索とソートのために分析済みrawの両方のフィールドが必要か
  • 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と名前)を埋め込みます。注文詳細を埋め込みます(@OneToManyなので、ElasticSearchの「ネスト」プロパティとして)。

@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、名前を含むネストされたproductを含む、より多くの注文の詳細を埋め込みます。

@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でアノテーションを付ける必要はありません。

product skuに@DocCodeを付けると、それが埋め込まれている場所でもコードと見なされることに注意してください。したがって、product skuが注文インデックスに埋め込まれている場合、そこでも@DocCodeプロパティと見なされます。

product 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でアノテーションを付けたいと考えています。これにより、プロパティに分析済みと未分析/rawの両方のフィールドが提供されます。分析済みフィールドをテキスト検索に使用し、未分析/rawフィールドをソートに使用できます(およびElasticSearch集計機能)。

customer nameに@DocSortableを付けると、それが埋め込まれている場所でもソート可能と見なされることに注意してください。したがって、customer nameが注文インデックスに埋め込まれている場合、そこでも@DocSortableと見なされます。

例: 顧客

顧客名でソートできるようにしたい。

@DocStore
@Entity
public class Customer {

  ...
  @DocSortable
  String name;
例: 製品

製品名でソートできるようにしたい(そして、製品SKUはコードなのでソートできます)。

@DocStore
@Entity
public class Product {

  @DocCode
  String sku;

  @DocSortable
  String name;

マッピング

@DocSortableプロパティは、追加の「raw」未分析フィールドでマッピングされます。

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

クエリの使用 - order by

Ebeanクエリを作成し、order by句を指定すると、Ebeanはorder by句を自動的に変換して、関連する「raw」フィールドがある場合はそれを使用します。

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":{}}}

クエリの使用 - Term式

「等しい」はElasticの「term」クエリに変換され、@DocSortableプロパティの場合、term式は関連する「raw」フィールドを使用します。

同様に、「より大きい」、「より小さい」、「以上」、「以下」も、利用可能な場合は「raw」フィールドも使用する範囲クエリに変換されます。

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 options - DOCS、FREQS、POSITIONS、OFFSETS

また、@DocCodeおよび@DocSortableの代わりに、codeおよびsortableを設定するフラグも提供します。

@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 {

マッピングの生成

比較的構造化されたORMドキュメントでElasticSearchを効果的に使用するには、適切なプロパティマッピング(型、コード、ソート可能など)を持つ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は起動時に、どのインデックスが存在するかを確認し、生成されたマッピングを使用して不足しているものをすべて作成します。

これは、開発/テスト中に使用されることが期待されています。

create=trueを使用するのは、dropCreateがfalseの場合のみであることに注意してください。

マッピングの例

例のアプリケーションの生成されたマッピングの例は、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" }
      }
    }
  }
}