Elasticsearch 对象映射

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

元模型对象映射

基于元模型的方法使用领域类型信息从 Elasticsearch 读取/写入数据。这允许为特定领域类型映射注册 Converter 实例。

映射注解概述

MappingElasticsearchConverter 使用元数据来驱动对象到文档的映射。元数据来自实体属性,这些属性可以被注解。

以下注解可用

  • @Document:应用于类级别,指示此类是映射到数据库的候选。最重要的属性有(完整属性列表请查阅 API 文档)

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

    • createIndex:在 repository 启动时是否创建索引的标志。默认值为 true。参见 使用相应的映射自动创建索引

  • @Id:应用于字段级别,标记用于身份目的的字段。

  • @Transient, @ReadOnlyProperty, @WriteOnlyProperty:详细信息请参见下一节 控制哪些属性被写入和读取到 Elasticsearch

  • @PersistenceConstructor:标记给定的构造函数(即使是包级保护的构造函数),用于从数据库实例化对象。构造函数参数按名称映射到检索到的 Document 中的键值。

  • @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:一个或多个内置日期格式,参见下一节 日期格式映射

    • pattern:一个或多个自定义日期格式,参见下一节 日期格式映射

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

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

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

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

映射元数据基础设施在独立于技术的 spring-data-commons 项目中定义。

控制哪些属性被写入和读取到 Elasticsearch

本节详细介绍了定义属性值是否写入或读取到 Elasticsearch 的注解。

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

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

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

日期格式映射

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

@Field 注解有两个属性定义了哪些日期格式信息被写入映射(另请参见 Elasticsearch 内置格式Elasticsearch 自定义日期格式

format 属性用于定义至少一个预定义格式。如果未定义,则使用默认值 _date_optional_timeepoch_millis

pattern 属性可用于添加额外的自定义格式字符串。如果只想使用自定义日期格式,必须将 format 属性设置为空 {}

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

注解 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。这是因为 Elasticsearch 7 中的更改

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

范围类型

当字段使用 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> 类型类有 Integer, Long, Float, Double, Date 以及实现 TemporalAccessor 接口的类。

映射字段名

如果没有进一步配置,Spring Data Elasticsearch 将使用对象的属性名称作为 Elasticsearch 中的字段名。这可以通过在该属性上使用 @Field 注解来更改单个字段的名称。

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

非字段支持属性

通常,实体中使用的属性是实体类的字段。在某些情况下,属性值可能在实体中计算,并且应该存储在 Elasticsearch 中。在这种情况下,getter 方法 (getProperty()) 可以用 @Field 注解标记,此外,方法必须用 @AccessType(AccessType.Type .PROPERTY) 注解标记。在这种情况下需要的第三个注解是 @WriteOnlyProperty,因为这种值只写入 Elasticsearch。一个完整的例子

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

其他属性注解

@IndexedIndexName

此注解可以设置在实体的 String 属性上。此属性不会写入映射,不会存储在 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),以便在第一次从存储中读取数据时 already 具有实体信息。
示例 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 中检索,或者文档检索可能完全失败。

地理空间类型

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

示例 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" } ]
}

Map

对于 Map 中的值,应用与聚合根相同的映射规则,涉及类型提示自定义转换。然而,Map 键必须是 String 才能由 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 }
     }
  }
}

自定义转换

查看上一节ConfigurationElasticsearchCustomConversions 允许注册领域类型和简单类型映射的特定规则。

示例 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 }
}
1 添加 Converter 实现。
2 设置用于将 DomainType 写入 Elasticsearch 的 Converter
3 设置用于从搜索结果中读取 DomainTypeConverter