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
:标记给定的构造函数(即使是包保护的构造函数)以在从数据库实例化对象时使用。构造函数参数按名称映射到检索到的文档中的键值。 -
@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 Clients)。例如,如果配置了 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 动态设置或写入写别名时非常有用。
将某个值放入这样的属性中并不会设置实体存储的索引!
映射规则
类型提示
映射使用嵌入在发送到服务器的文档中的类型提示来实现通用类型映射。这些类型提示在文档中表示为 _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
、接口或实际值的类型与属性声明不匹配,否则不会为嵌套对象编写类型提示。
禁用类型提示
在索引已经存在但未在其映射中定义类型提示且映射模式设置为严格的情况下,可能需要禁用类型提示的写入。在这种情况下,写入类型提示将产生错误,因为无法自动添加字段。
可以通过在派生自 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
(GeoJSON 点) -
GeoJsonMultiPoint
(GeoJSON 多点) -
GeoJsonLineString
(GeoJSON 线) -
GeoJsonMultiLineString
(GeoJSON 多线) -
GeoJsonPolygon
(GeoJSON 多边形) -
GeoJsonMultiPolygon
(GeoJSON 多多边形) -
GeoJsonGeometryCollection
(GeoJSON 几何集合)
集合
对于集合内部的值,在涉及 类型提示 和 自定义转换 时,应用与聚合根相同的映射规则。
示例 5. 集合
public class Person {
// ...
List<Person> friends;
}
{
// ...
"friends" : [ { "firstname" : "Kyle", "lastname" : "Reese" } ]
}
映射
对于 Map 内部的值,在涉及 类型提示 和 自定义转换 时,应用与聚合根相同的映射规则。然而,Map 的键需要是字符串才能被 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
。