Elasticsearch オブジェクトマッピング

Spring Data Elasticsearch オブジェクトマッピングは、Java オブジェクト (ドメインエンティティ) を Elasticsearch に格納されている JSON 表現にマッピングし、その逆を行うプロセスです。このマッピングに内部的に使用されるクラスは MappingElasticsearchConverter です。

メタモデルオブジェクトのマッピング

メタモデルベースのアプローチでは、Elasticsearch からの読み取り / 書き込みにドメイン型情報を使用します。これにより、特定のドメイン型 マッピングに Converter インスタンスを登録できます。

マッピングアノテーションの概要

MappingElasticsearchConverter はメタデータを使用して、オブジェクトからドキュメントへのマッピングを実行します。メタデータは、アノテーションを付けることができるエンティティのプロパティから取得されます。

次のアノテーションが利用可能です。

  • @Document: クラスレベルで適用され、このクラスがデータベースへのマッピングの候補であることを示します。最も重要な属性は次のとおりです (属性の完全なリストについては API ドキュメントを確認してください)。

    • indexName: このエンティティを保存するインデックスの名前。これには、"log-#{T(java.time.LocalDate).now().toString()}" のような SpEL テンプレート式を含めることができます

    • createIndex: リポジトリのブートストラップ時にインデックスを作成するかどうかを示すフラグ。デフォルト値は true です。対応するマッピングを使用したインデックスの自動作成を参照

  • @Id: ID 目的に使用されるフィールドをマークするためにフィールドレベルで適用されます。

  • @Transient@ReadOnlyProperty@WriteOnlyProperty: 詳細については、次の "Elasticsearch に書き込まれるプロパティと Elasticsearch から読み取られるプロパティを制御する" セクションを参照してください。

  • @PersistenceConstructor: データベースからオブジェクトをインスタンス化するときに使用する特定のコンストラクター (パッケージで protected コンストラクターも含む) をマークします。コンストラクターの引数は、取得されたドキュメント内のキー値に名前によってマップされます。

  • @Field: フィールドレベルで適用され、フィールドのプロパティを定義するため、ほとんどの属性はそれぞれの Elasticsearch マッピング (英語) 定義にマップされます (次のリストは完全ではありません。完全なリファレンスについては、アノテーション Javadoc を確認してください)。

    • name: Elasticsearch ドキュメント内で表されるフィールドの名前。設定されていない場合は、Java フィールド名が使用されます。

    • type: フィールド型は、Text、Keyword、Long、Integer、Short、Byte、Double、Float、Half_Float、Scaled_Float、Date、Date_Nanos、Boolean、Binary、Integer_Range、Float_Range、Long_Range、Double_Range、Date_Range、Ip_Range、Object、Nested、Ip、TokenCount、Percolator、Flattened、Search_As_You_Type のいずれかになります。Elasticsearch マッピング型 (英語) を参照してください。フィールド型が指定されていない場合は、デフォルトで FieldType.Auto になります。つまり、プロパティのマッピングエントリは書き込まれず、このプロパティの最初のデータが格納されると、Elasticsearch によってマッピングエントリが動的に追加されます (動的マッピングルールについては、Elasticsearch のドキュメントを確認してください)。

    • format: 1 つ以上の組み込み日付形式については、次のセクション日付形式のマッピングを参照してください。

    • pattern: 1 つ以上のカスタム日付形式については、次のセクション日付形式のマッピングを参照してください。

    • store: 元のフィールド値を Elasticsearch に格納するかどうかを示すフラグ。デフォルト値は false です。

    • analyzersearchAnalyzernormalizer はカスタムアナライザーとノーマライザーを指定します。

  • @GeoPoint: フィールドを geo_point データ型としてマークします。フィールドが GeoPoint クラスのインスタンスの場合は省略できます。

  • @ValueConverter は、指定されたプロパティの変換に使用されるクラスを定義します。登録された Spring Converter とは異なり、これはアノテーション付きプロパティのみを変換し、指定された型のすべてのプロパティを変換するわけではありません。

マッピングメタデータインフラストラクチャは、テクノロジに依存しない別の spring-data-commons プロジェクトで定義されます。

Elasticsearch に書き込まれるプロパティと Elasticsearch から読み取られるプロパティを制御する

このセクションでは、プロパティの値が Elasticsearch に書き込まれるか、Elasticsearch から読み取られるかを定義するアノテーションについて詳しく説明します。

@Transient: このアノテーションが付けられたプロパティはマッピングに書き込まれず、その値は Elasticsearch に送信されず、Elasticsearch からドキュメントが返されるときに、このプロパティは結果のエンティティに設定されません。

@ReadOnlyProperty: このアノテーションを持つプロパティの値は Elasticsearch に書き込まれませんが、データを返すときに、プロパティには Elasticsearch からドキュメントに返された値が入力されます。これを使用する 1 つの例は、インデックスマッピングで定義されたランタイムフィールドです。

@WriteOnlyProperty: このアノテーションを持つプロパティの値は Elasticsearch に保存されますが、ドキュメントの読み取り時には値は設定されません。これは、たとえば、Elasticsearch インデックスに入力する必要があるが、他の場所では使用されない合成フィールドに使用できます。

日付形式のマッピング

TemporalAccessor から派生するプロパティ、または型 java.util.Date のプロパティには、型 FieldType.Date の @Field アノテーションがあるか、カスタムコンバーターがこの型に登録されている必要があります。この段落では、FieldType.Date の使用箇所について説明します。

@Field アノテーションには、どの日付形式情報をマッピングに書き込むかを定義する 2 つの属性があります。(Elasticsearch 組み込みフォーマット (英語) および Elasticsearch カスタム日付形式 (英語) も参照)

format 属性は、事前定義された形式の少なくとも 1 つを定義するために使用されます。定義されていない場合は、デフォルト値の _date_optional_time および epoch_millis が使用されます。

pattern 属性を使用して、追加のカスタム形式文字列を追加できます。カスタム日付形式のみを使用する場合は、format プロパティを空の {} に設定する必要があります。

次の表は、さまざまな属性とその値から作成されたマッピングを示しています。

アノテーション Elasticsearch マッピングのフォーマット文字列

@Field(型 = フィールド型 . 日付)

"date_optional_time | |epoch_millis",

@Field(型 = フィールド型 . 日付、フォーマット = 日付形式 . 基本日付)

「基本的な日付」

@Field(型 = フィールド型 . 日付、形式 ={DateFormat.basic_date, DateFormat.basic_time})

" 基本日付 | | 基本時間 "

@Field(型 = フィールド型 . 日付、パターン =” dd.MM.uuuu”)

"date_optional_time | |epoch_millis||dd.MM.uuuu",

@Field(型 = フィールド型 . 日付、format={}, パターン =” dd.MM.uuuu”)

"dd.MM.uuuu"

カスタムの日付形式を使用している場合は、年に yyyy ではなく uuuu を使用する必要があります。これは、Elasticsearch 7 の変更 (英語) によるものです。

事前定義された値とそのパターンの完全なリストについては、org.springframework.data.elasticsearch.annotations.DateFormat 列挙型のコードを確認してください。

範囲の種類

フィールドに Integer_Range、Float_Range、Long_Range、Double_Range、Date_RangeIp_Range のいずれかの型がアノテーション付けされている場合、フィールドは Elasticsearch 範囲にマップされるクラスのインスタンスである必要があります。例:

class SomePersonData {

    @Field(type = FieldType.Integer_Range)
    private ValidAge validAge;

    // getter and setter
}

class ValidAge {
    @Field(name="gte")
    private Integer from;

    @Field(name="lte")
    private Integer to;

    // getter and setter
}

代わりに、Spring Data、Elasticsearch は Range<T> クラスを提供するため、前の例は次のように記述できます。

class SomePersonData {

    @Field(type = FieldType.Integer_Range)
    private Range<Integer> validAge;

    // getter and setter
}

型 <T> でサポートされるクラスは、IntegerLongFloatDoubleDate および TemporalAccessor インターフェースを実装するクラスです。

マップされたフィールド名

追加の設定を行わない場合、Spring Data、Elasticsearch はオブジェクトのプロパティ名を Elasticsearch のフィールド名として使用します。これは、そのプロパティの @Field アノテーションを使用して個々のフィールドごとに変更できます。

クライアントの構成 ( Elasticsearch クライアント ) で FieldNamingStrategy を定義することもできます。たとえば、SnakeCaseFieldNamingStrategy が構成されている場合、オブジェクトのプロパティ sampleProperty は Elasticsearch の sample_property にマップされます。FieldNamingStrategy はすべてのエンティティに適用されます。プロパティに @Field を使用して特定の名前を設定することで上書きできます。

フィールドに依存しないプロパティ

通常、エンティティで使用されるプロパティは、エンティティクラスのフィールドです。プロパティ値がエンティティで計算され、Elasticsearch に格納される必要がある場合があります。この場合、getter メソッド (getProperty()) は @Field アノテーションでアノテーションを付けることができますが、それに加えて、メソッドは @AccessType(AccessType.Type .PROPERTY) アノテーションでアノテーションを付ける必要があります。このような場合に必要となる 3 番目のアノテーションは @WriteOnlyProperty です。このような値は Elasticsearch にのみ書き込まれるためです。完全な例:

@Field(type = Keyword)
@WriteOnlyProperty
@AccessType(AccessType.Type.PROPERTY)
public String getProperty() {
	return "some value that is calculated here";
}

その他のプロパティのアノテーション

@IndexedIndexName

このアノテーションは、エンティティの文字列プロパティに設定できます。このプロパティはマッピングに書き込まれず、Elasticsearch に保存されず、その値は Elasticsearch ドキュメントから読み取られません。エンティティが永続化された後 (たとえば、ElasticsearchOperations.save(T entity) の呼び出しを使用)、その呼び出しから返されるエンティティには、そのプロパティにエンティティが保存されたインデックスの名前が含まれます。これは、インデックス名が Bean によって動的に設定される場合、または書き込みエイリアスに書き込む場合に便利です。

このようなプロパティに値を入力しても、エンティティが格納されるインデックスは設定されません。

マッピングルール

型ヒント

マッピングでは、サーバーに送信されたドキュメントに埋め込まれた型ヒントを使用して、ジェネリクス型マッピングを可能にします。これらの型ヒントは、ドキュメント内で _class 属性として表され、集約ルートごとに書き込まれます。

例 1: 型ヒント
public class Person {              (1)
  @Id String id;
  String firstname;
  String lastname;
}
{
  "_class" : "com.example.Person", (1)
  "id" : "cb7bef",
  "firstname" : "Sarah",
  "lastname" : "Connor"
}
1 デフォルトでは、ドメイン型のクラス名が型 ヒントに使用されます。

カスタム情報を保持するように型ヒントを構成できます。これを行うには、@TypeAlias アノテーションを使用します。

ストアから最初にデータを読み取るときにエンティティ情報を利用できるように、必ず @TypeAlias を持つ型を初期エンティティセット (AbstractElasticsearchConfiguration#getInitialEntitySet) に追加してください。
例 2: エイリアスを使用した入力ヒント
@TypeAlias("human")                (1)
public class Person {

  @Id String id;
  // ...
}
{
  "_class" : "human",              (1)
  "id" : ...
}
1 構成されたエイリアスは、エンティティを書き込むときに使用されます。
プロパティの型が Object であるか、インターフェースまたは実際の値の型がプロパティの宣言と一致しない場合を除き、ネストされたオブジェクトの型ヒントは書き込まれません。
型ヒントの無効化

使用する必要があるインデックスが、そのマッピングで型 ヒントが定義されておらず、マッピングモードが strict に設定されていない状態ですでに存在する場合、型 ヒントの書き込みを無効にすることが必要な場合があります。この場合、フィールドは自動的に追加できないため、型 ヒントを記述するとエラーが発生します。

型ヒントは、AbstractElasticsearchConfiguration から派生した構成クラスのメソッド writeTypeHints() をオーバーライドすることで、アプリケーション全体で無効にできます ( Elasticsearch クライアントを参照)。

代わりに、@Document アノテーションを使用して単一のインデックスに対して無効にすることもできます。

@Document(indexName = "index", writeTypeHint = WriteTypeHint.FALSE)
型ヒントを無効にすることは強くお勧めしません。必要な場合にのみ無効にしてください。型ヒントを無効にすると、多態的なデータの場合に Elasticsearch からドキュメントが正しく取得されなかったり、ドキュメントの取得が完全に失敗したりする可能性があります。

地理空間型

Point や GeoPoint などの地理空間型は、緯度 / 経度のペアに変換されます。

例 3: 地理空間型
public class Address {
  String city, street;
  Point location;
}
{
  "city" : "Los Angeles",
  "street" : "2800 East Observatory Road",
  "location" : { "lat" : 34.118347, "lon" : -118.3026284 }
}

GeoJson 型

Spring Data Elasticsearch は、インターフェース GeoJson とさまざまなジオメトリの実装を提供することで、GeoJson 型をサポートします。これらは、GeoJson 仕様に従って Elasticsearch ドキュメントにマップされます。エンティティの対応するプロパティは、インデックスマッピングが書き込まれるときに、インデックスマッピングで geo_shape として指定されます。(Elasticsearch ドキュメント (英語) もチェックしてみてください)

例 4: GeoJson 型
public class Address {

  String city, street;
  GeoJsonPoint location;
}
{
  "city": "Los Angeles",
  "street": "2800 East Observatory Road",
  "location": {
    "type": "Point",
    "coordinates": [-118.3026284, 34.118347]
  }
}

次の GeoJson 型が実装されています。

  • GeoJsonPoint

  • GeoJsonMultiPoint

  • GeoJsonLineString

  • GeoJsonMultiLineString

  • GeoJsonPolygon

  • GeoJsonMultiPolygon

  • GeoJsonGeometryCollection

コレクション

コレクション内の値には、型ヒントおよびカスタム変換に関して、集約ルートと同じマッピングルールが適用されます。

例 5: コレクション
public class Person {

  // ...

  List<Person> friends;

}
{
  // ...

  "friends" : [ { "firstname" : "Kyle", "lastname" : "Reese" } ]
}

マップ

マップ内の値については、型ヒントカスタム変換に関しては、集約ルートと同じマッピングルールが適用されます。ただし、マップキーは、Elasticsearch で処理される文字列である必要があります。

例 6: コレクション
public class Person {

  // ...

  Map<String, Address> knownLocations;

}
{
  // ...

  "knownLocations" : {
    "arrivedAt" : {
       "city" : "Los Angeles",
       "street" : "2800 East Observatory Road",
       "location" : { "lat" : 34.118347, "lon" : -118.3026284 }
     }
  }
}

カスタム変換

前のセクションの Configuration を見ると、ElasticsearchCustomConversions では、ドメインと単純型をマッピングするための特定のルールを登録できます。

例 7: メタモデルオブジェクトマッピングの設定
@Configuration
public class Config extends ElasticsearchConfiguration  {

	@Override
	public ClientConfiguration clientConfiguration() {
		return ClientConfiguration.builder() //
				.connectedTo("localhost:9200") //
				.build();
	}

  @Bean
  @Override
  public ElasticsearchCustomConversions elasticsearchCustomConversions() {
    return new ElasticsearchCustomConversions(
      Arrays.asList(new AddressToMap(), new MapToAddress()));       (1)
  }

  @WritingConverter                                                 (2)
  static class AddressToMap implements Converter<Address, Map<String, Object>> {

    @Override
    public Map<String, Object> convert(Address source) {

      LinkedHashMap<String, Object> target = new LinkedHashMap<>();
      target.put("ciudad", source.getCity());
      // ...

      return target;
    }
  }

  @ReadingConverter                                                 (3)
  static class MapToAddress implements Converter<Map<String, Object>, Address> {

    @Override
    public Address convert(Map<String, Object> source) {

      // ...
      return address;
    }
  }
}
{
  "ciudad" : "Los Angeles",
  "calle" : "2800 East Observatory Road",
  "localidad" : { "lat" : 34.118347, "lon" : -118.3026284 }
}
1Converter 実装を追加します。
2DomainType を Elasticsearch に書き込むために使用する Converter を設定します。
3 検索結果から DomainType を読み出す際に使用する Converter を設定します。