跳到主要内容
版本:6.1.1

Elasticsearch 对象映射

Doubao 中英对照 Elasticsearch Object Mapping

Spring Data Elasticsearch 对象映射是这样一个过程:它将一个 Java 对象——领域实体——映射为存储在 Elasticsearch 中的 JSON 表示形式,反之亦然。内部用于此映射的类是 MappingElasticsearchConverter。

元模型对象映射​

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

映射注解概览​

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

以下注解可用:

  • @Document:应用于类级别,表明该类是映射到数据库的候选。最重要的属性包括(完整属性列表请查看 API 文档):

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

    • createIndex:是否在仓库初始化时创建索引的标志。默认值为 true。请参阅自动创建索引及相应映射

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

  • @Transient、@ReadOnlyProperty、@WriteOnlyProperty:详细信息请参阅以下章节控制哪些属性写入 Elasticsearch 以及从 Elasticsearch 读取。

  • @PersistenceConstructor:标记给定的构造函数——即使是包级私有构造函数——用于从数据库实例化对象时使用。构造函数参数按名称映射到检索到的文档中的键值。

  • @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 读取​

本节详细介绍用于定义属性值是否写入 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_time 和 epoch_millis。

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

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

注解Elasticsearch 映射中的格式字符串
@Field(type=FieldType.Date)"date_optional_time
@Field(type=FieldType.Date, format=DateFormat.basic_date)"basic_date"
@Field(type=FieldType.Date, format={DateFormat.basic_date, DateFormat.basic_time})"basic_date
@Field(type=FieldType.Date, pattern="dd.MM.uuuu")"date_optional_time
@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 注解来为单个字段更改这一点。

也可以在客户端配置中定义一个 FieldNamingStrategy(请参阅Elasticsearch 客户端)。例如,如果配置了 SnakeCaseFieldNamingStrategy,则对象的 sampleProperty 属性将被映射到 Elasticsearch 中的 sample_property。FieldNamingStrategy 适用于所有实体;可以通过在属性上用 @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 动态设置、或写入写别名时,这很有用。

向这样的属性中放入某个值并不会设置实体被存储到的索引!

映射规则​

类型提示​

映射使用嵌入在发送给服务器的文档中的 type hints 来支持泛型类型映射。这些类型提示在文档中表示为 _class 属性,并为每个聚合根写入。

示例 1. 类型提示

public class Person {              1
@Id String id;
String firstname;
String lastname;
}
{
"_class" : "com.example.Person", 1
"id" : "cb7bef",
"firstname" : "Sarah",
"lastname" : "Connor"
}
  • 默认情况下,类型提示使用领域类型的类名。

类型提示可以配置为保存自定义信息。请使用 @TypeAlias 注解来实现这一点。

备注

请确保将带有 @TypeAlias 的类型添加到初始实体集合(AbstractElasticsearchConfiguration#getInitialEntitySet)中,以便在首次从存储读取数据时实体信息已可用。

示例 2. 带别名的类型提示

@TypeAlias("human")                1
public class Person {

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

备注

除非属性类型是 Object、接口,或者实际值类型与属性声明不匹配,否则不会为嵌套对象写入类型提示。

禁用类型提示​

当要使用的索引已经存在、其映射中未定义类型提示且映射模式被设置为 strict 时,可能需要禁用类型提示的写入。在这种情况下,写入类型提示将产生错误,因为该字段无法被自动添加。

可以通过在派生自 AbstractElasticsearchConfiguration 的配置类中重写 writeTypeHints() 方法来为整个应用禁用类型提示(请参阅Elasticsearch 客户端)。

作为替代,可以使用 @Document 注解为单个索引禁用类型提示:

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

我们强烈建议不要禁用类型提示。仅在被迫时才这样做。在多态数据的情况下,禁用类型提示可能导致文档无法从 Elasticsearch 正确检索,或者文档检索可能完全失败。

地理空间类型​

像 Point 和 GeoPoint 这样的地理空间类型会被转换为 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

集合​

对于集合中的值,在 type hints 和自定义转换方面,应用与聚合根相同的映射规则。

示例 5. 集合

public class Person {

// ...

List<Person> friends;

}
{
// ...

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

Map 结构​

对于 Map 中的值,在 type hints 和自定义转换方面,应用与聚合根相同的映射规则。不过,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 }
}
}
}

自定义转换​

查看上一节中的 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 }
}
  • 添加 Converter 实现。

  • 设置用于将 DomainType 写入 Elasticsearch 的 Converter。

  • 设置用于从搜索结果读取 DomainType 的 Converter。