Elasticsearch Object Mapping

Spring Data Elasticsearch 对象映射是将 Java 对象(领域实体)映射到存储在 Elasticsearch 中的 JSON 表示形式并返回的过程。用于此映射的类在内部是 MappingElasticsearchConverter

Meta Model Object Mapping

基于元模型的方法使用领域类型信息从 Elasticsearch 中读取/写入。这允许为特定领域类型映射注册“转换器”实例。

Mapping Annotation Overview

MappingElasticsearchConverter 使用元数据来推动将对象映射到文档。元数据来自实体的属性,这些属性可以添加注释。

以下注释可用:

  • @Document:在类级别应用,表示此类可映射到数据库。最重要的属性是(查看 API 文档中的完整属性列表):

    • indexName:存储此实体的索引的名称。它可以包含像 "log-#{T(java.time.LocalDate).now().toString()}" 这样的 SpEL 模板表达式。

    • createIndex:标记是否在存储库引导时创建索引。默认值为 true。请参阅 Automatic creation of indices with the corresponding mapping

  • @Id: 在字段级别应用以标记用于标识目的的字段。

  • @Transient, @ReadOnlyProperty, @WriteOnlyProperty:有关详细信息,请参阅以下小节 Controlling which properties are written to and read from Elasticsearch

  • @PersistenceConstructor: 标记给定的构造函数(甚至是包保护的构造函数),以便在从数据库实例化对象时使用。构造函数参数按名称映射到检索到的文档中的键值。

  • @Field:在字段级别应用,定义字段的属性,大多数属性都映射到各自的 Elasticsearch Mapping 定义(以下列表不完整,请查看注释 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 Mapping Types。如果未指定字段类型,则默认为 FieldType.Auto。这意味着,尚未为该属性写入映射条目,并且 Elasticsearch 将在为此属性存储第一个数据时动态地添加一个映射条目(请查看 Elasticsearch 文档以了解动态映射规则)。

    • format:一种或多种内置日期格式,请参阅下一小节 Date format mapping

    • pattern: 一个或多个自定义日期格式,请参阅下一部分 Date format mapping.

    • store: 标记原始字段值是否应存储在 Elasticsearch 中,默认值为 false

    • analyzer, searchAnalyzer, normalizer 用于指定自定义分析器和规范化器。

  • @GeoPoint: 将字段标记为 geo_point 数据类型。如果字段是 GeoPoint 类的实例,则可以省略。

  • @ValueConverter 定义要用于转换给定属性的类。与注册的 Spring Converter 不同,这仅转换带注释的属性,而不转换给定类型的每个属性。

映射元数据基础设施定义在一个独立的 spring-data-commons 项目中,该项目与技术无关。

Controlling which properties are written to and read from Elasticsearch

本节详细介绍定义是否将属性值写入或从 Elasticsearch 中读取的注释。

@Transient:使用此注释注释的属性不会写入映射,其值不会发送到 Elasticsearch,并且当从 Elasticsearch 返回文档时,此属性不会设置在结果实体中。

@ReadOnlyProperty:使用此注释的属性不会将其值写入 Elasticsearch,但在返回数据时,属性会填充 Elasticsearch 中文档中返回的值。一个用例是索引映射中定义的运行时字段。

@WriteOnlyProperty:使用此注释的属性将在 Elasticsearch 中存储其值,但读取文档时不会使用任何值对其进行设置。例如,这可用于综合字段,这些字段应进入 Elasticsearch 索引,但不会在其他地方使用。

Date format mapping

源自 TemporalAccessor 或类型为 java.util.Date 的属性必须具有类型为 FieldType.Date@Field 注释,或者必须为该类型注册自定义转换器。本段描述了 FieldType.Date 的用法。

`@Field`注解有两个属性,它们定义将哪些日期格式信息写入映射(另请参见 Elasticsearch Built In FormatsElasticsearch Custom Date Formats)。

format 属性用于定义至少一种预定义的格式。如果没有定义,那么将使用 _date_optional_timeepoch_millis 的默认值。

pattern 属性可用于添加其他自定义格式字符串。如果您只希望使用自定义日期格式,则必须将 format 属性设置为一个空集 {}

下表显示了不同的属性以及从其值创建的映射:

annotation Elasticsearch 映射中的格式字符串

@Field(type=FieldType.Date)

"date_optional_time

epoch_millis",

@Field(type=FieldType.Date, format=DateFormat.basic_date)

"basic_date"

@Field(type=FieldType.Date, format={DateFormat.basic_date, DateFormat.basic_time})

"basic_date

basic_time"

@Field(type=FieldType.Date, pattern="dd.MM.uuuu")

"date_optional_time

epoch_millis

dd.MM.uuuu",

@Field(type=FieldType.Date, format={}, pattern="dd.MM.uuuu")

"dd.MM.uuuu"

如果您使用的是自定义日期格式,则需要对年份使用 uuuu 而不是 yyyy。这是由于一个 change in Elasticsearch 7

查看 org.springframework.data.elasticsearch.annotations.DateFormat 枚举的代码,以获取预定义值及其模式的完整列表。

Range types

当字段添加了 Integer_Range、Float_Range、Long_Range、Double_Range、Date_Range、Ip_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` 接口的类。

Mapped field names

在没有进一步配置的情况下,Spring Data Elasticsearch 会将对象的属性名称用作 Elasticsearch 中的字段名称。这可以通过对该属性使用 @Field 注释来对各个字段进行更改。

也可以在客户端的配置中定义 FieldNamingStrategy (Elasticsearch Clients)。例如,如果配置了 SnakeCaseFieldNamingStrategy,则对象 sampleProperty 将被映射到 Elasticsearch 中的 sample_propertyFieldNamingStrategy 适用于所有实体;可以通过在属性上使用 @Field 设置特定名称来覆盖它。

Non-field-backed properties

通常,实体中使用的属性是实体类的字段。在某些情况下,属性值是在实体中计算的,并且应存储在 Elasticsearch 中。在这种情况下,getter 方法 (getProperty() 除了必须使用 @AccessType(AccessType.Type.PROPERTY) 注释外,还可以使用 @Field 注释。在此类情况下所需的第三个注释是 @WriteOnlyProperty,因为这样值才会写入 Elasticsearch。一个完整的示例:

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

Other property annotations

@IndexedIndexName

此注释可以设置在实体的 String 属性上。此属性将不会写入映射,它将不会存储在 Elasticsearch 中,并且它的值将不会从 Elasticsearch 文档中读取。在持久实体后,例如通过对 ElasticsearchOperations.save(T entity) 的调用,从该调用返回的实体将包含该属性中保存实体的索引名称。当索引名称由 Bean 动态设置时,或者当写入只写别名时,这一点非常有用。

将某些值放入这样的属性中不会设置实体存储其中的索引!

Mapping Rules

Type Hints

映射使用嵌入在发送到服务器的文档中的 类型提示 来允许通用类型映射。这些类型提示在文档中表示为 _class 属性,并且为每个聚合根编写。

Example 1. Type Hints
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),以便在首次从存储中读取数据时已掌握实体信息。

Example 2. Type Hints with Alias
@TypeAlias("human")                1
public class Person {

  @Id String id;
  // ...
}
{
  "_class" : "human",              1
  "id" : ...
}
1 配置的别名用于编写实体。

以下情况下不会为嵌套对象编写类型提示:属性类型不是 Object,接口或实际值类型与属性声明不匹配。

Disabling Type Hints

当应该使用的索引已经存在、在它的映射中没有定义类型提示并且映射模式设置为严格时,可能有必要禁用类型提示的写入。在这种情况下,编写类型提示将产生错误,因为无法自动添加字段。

可以通过在从 AbstractElasticsearchConfiguration 派生的配置类中重写方法 writeTypeHints() 为整个应用程序禁用类型提示(请参阅 Elasticsearch Clients)。

作为一种替代方案,可以使用 @Document 注释为单个索引禁用它们:

@Document(indexName = "index", writeTypeHint = WriteTypeHint.FALSE)

我们强烈建议不要禁用类型提示。只有在必要情况下才执行此操作。禁用类型提示可能导致 Elasticsearch 在多态数据情况下无法正确检索文档,或者文档检索可能完全失败。

Geospatial Types

PointGeoPoint 等地理空间类型被转换为 lat/lon 对。

Example 3. Geospatial types
public class Address {
  String city, street;
  Point location;
}
{
  "city" : "Los Angeles",
  "street" : "2800 East Observatory Road",
  "location" : { "lat" : 34.118347, "lon" : -118.3026284 }
}

GeoJson Types

Spring Data Elasticsearch通过提供界面`GeoJson`以及针对不同几何的不同实现支持GeoJson类型。它们根据GeoJson规范映射到Elasticsearch文档。当写入索引映射时,将实体的相应属性在索引映射中指定为`geo_shape`。(也请检查 Elasticsearch documentation

Example 4. GeoJson types
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

Collections

对于集合内的值,当涉及到_type hints_和Custom Conversions时,应用与聚合根相同的映射规则。

Example 5. Collections
public class Person {

  // ...

  List<Person> friends;

}
{
  // ...

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

Maps

对于映射内的值,当涉及到_type hints_和Custom Conversions时,应用与聚合根相同的映射规则。但是,映射键需要是字符串才能被Elasticsearch处理。

Example 6. Collections
public class Person {

  // ...

  Map<String, Address> knownLocations;

}
{
  // ...

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

Custom Conversions

通过 previous section ElasticsearchCustomConversions 中的 Configuration,可以注册特定规则,以映射域和简单类型。

Example 7. Meta Model Object Mapping Configuration
@Configuration
public class Config extends ElasticsearchConfiguration  {

	@NonNull
	@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 }
}
1 Add Converter implementations.
2 设置用于将 DomainType 写入 Elasticsearch 的 Converter
3 设置用于从搜索结果中读取 DomainTypeConverter