Elasticsearch 对象映射
Spring Data Elasticsearch 对象映射是将 Java 对象(域实体)映射到存储在 Elasticsearch 中的 JSON 表示并返回的过程。内部用于此映射的类是 MappingElasticsearchConverter。
元模型对象映射
基于元模型的方法使用域类型信息从 Elasticsearch 读取/写入数据。这允许为特定的域类型映射注册 Converter 实例。
映射注解概述
MappingElasticsearchConverter 使用元数据来驱动对象到文档的映射。元数据取自实体的属性,这些属性可以进行注解。
可用的注解如下
-
@Document:应用于类级别,表示此类别是映射到数据库的候选。最重要的属性是(有关完整属性列表,请查看 API 文档)-
indexName:存储此实体的索引名称。这可以包含 SpEL 模板表达式,例如"log-#{T(java.time.LocalDate).now().toString()}" -
createIndex:在存储库引导时是否创建索引的标志。默认值为 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定义一个类,用于转换给定属性。与注册的 SpringConverter不同,这只转换带注解的属性,而不是给定类型的每个属性。
映射元数据基础设施在独立的 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_time 和 epoch_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 注解,可以更改单个字段的名称。
还可以在客户端配置中定义 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";
}
映射规则
类型提示
映射使用嵌入在发送到服务器的文档中的“类型提示”来允许泛型类型映射。这些类型提示在文档中表示为 _class 属性,并为每个聚合根写入。
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),以便在第一次从存储中读取数据时就已经有可用的实体信息。 |
@TypeAlias("human") (1)
public class Person {
@Id String id;
// ...
}
{
"_class" : "human", (1)
"id" : ...
}
| 1 | 在写入实体时使用配置的别名。 |
除非属性类型是 Object、接口或实际值类型与属性声明不匹配,否则不会为嵌套对象写入类型提示。 |
禁用类型提示
当要使用的索引已经存在,且其映射中未定义类型提示,并且映射模式设置为严格时,可能需要禁用类型提示的写入。在这种情况下,写入类型提示将产生错误,因为无法自动添加该字段。
可以通过在继承自 AbstractElasticsearchConfiguration 的配置类中重写 writeTypeHints() 方法来禁用整个应用程序的类型提示(请参阅Elasticsearch 客户端)。
作为替代,可以通过 @Document 注解禁用单个索引的类型提示。
@Document(indexName = "index", writeTypeHint = WriteTypeHint.FALSE)
| 我们强烈建议不要禁用类型提示。只有在被迫时才这样做。禁用类型提示可能导致多态数据情况下无法正确从 Elasticsearch 检索文档,或者文档检索可能完全失败。 |
地理空间类型
地理空间类型,如 Point 和 GeoPoint 被转换为 纬度/经度 对。
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 文档)
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
集合
当涉及到 类型提示 和自定义转换时,集合内部的值应用与聚合根相同的映射规则。
public class Person {
// ...
List<Person> friends;
}
{
// ...
"friends" : [ { "firstname" : "Kyle", "lastname" : "Reese" } ]
}
映射
对于 Map 中的值,当涉及到 类型提示 和自定义转换时,应用与聚合根相同的映射规则。但是,Map 键需要是 String 才能被 Elasticsearch 处理。
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 允许注册用于映射域和简单类型的特定规则。
@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 | 设置用于从搜索结果读取 DomainType 的 Converter。 |