原标题:Spring认证中国教育管理中心-Spring Data MongoDB教程十三(内容来源:Spring中国教育管理中心)
18.1.4.Kotlin 支持
Spring Data 调整了 Kotlin 的细节以允许创建和更改对象。
Kotlin 对象创建
Kotlin 类支持实例化,默认情况下所有类都是不可变的,并且需要显式属性声明来定义可变属性。考虑以下data类Person:
代码语言:javascript复制data class Person(val id: String, val name: String)
上面的类编译为具有显式构造函数的典型类。我们可以通过添加另一个构造函数来自定义这个类,并使用注释@PersistenceConstructor来指示构造函数首选项:
代码语言:javascript复制data class Person(var id: String, val name: String) {
@PersistenceConstructor
constructor(id: String) : this(id, "unknown")
}
Kotlin 通过允许在未提供参数时使用默认值来支持参数可选性。当 Spring Data 检测到具有参数默认值的构造函数时,如果数据存储不提供值(或简单地返回null),它就会使这些参数不存在,因此 Kotlin 可以应用参数默认值。考虑以下应用参数默认值的类name
代码语言:javascript复制data class Person(var id: String, val name: String = "unknown")
每次name参数不是结果的一部分或其值为 时null,则name默认为unknown。
Kotlin 数据类的属性填充
在 Kotlin 中,默认情况下所有类都是不可变的,并且需要明确的属性声明来定义可变属性。考虑以下data类Person:
代码语言:javascript复制data class Person(val id: String, val name: String)
这个类实际上是不可变的。它允许创建新实例,因为 Kotlin 生成copy(…)创建新对象实例的方法,该方法从现有对象复制所有属性值并将作为参数提供的属性值应用到该方法。
Kotlin 覆盖属性
Kotlin 允许声明属性覆盖来改变子类中的属性。
代码语言:javascript复制open class SuperType(open var field: Int)
class SubType(override var field: Int = 1) :
SuperType(field) {
}
这样的安排呈现了两个名称为 的属性field。Kotlin 为每个类中的每个属性生成属性访问器(getter 和 setter)。实际上,代码如下所示:
代码语言:javascript复制public class SuperType {
private int field;
public SuperType(int field) {
this.field = field;
}
public int getField() {
return this.field;
}
public void setField(int field) {
this.field = field;
}
}
public final class SubType extends SuperType {
private int field;
public SubType(int field) {
super(field);
this.field = field;
}
public int getField() {
return this.field;
}
public void setField(int field) {
this.field = field;
}
}
getter 和 setterSubType只在set 上,SubType.field而不是SuperType.field. 在这种安排中,使用构造函数是设置的唯一默认方法SuperType.field。添加方法 to SubTypeset SuperType.fieldviathis.SuperType.field = …是可能的,但不属于支持的约定。属性覆盖在某种程度上会产生冲突,因为属性共享相同的名称但可能代表两个不同的值。我们通常建议使用不同的属性名称。
Spring Data 模块通常支持包含不同值的覆盖属性。从编程模型的角度来看,需要考虑以下几点:
- 应该保留哪个属性(默认为所有声明的属性)?您可以通过使用 注释这些属性来排除属性@Transient。
- 如何表示数据存储中的属性?对不同的值使用相同的字段/列名称通常会导致数据损坏,因此您应该使用明确的字段/列名称来注释至少一个属性。
- using@AccessType(PROPERTY)不能使用,因为不能设置超级属性。
18.2.基于约定的映射
MappingMongoConverter当没有提供额外的映射元数据时,有一些将对象映射到文档的约定。这些约定是:
- 简短的 Java 类名称以下列方式映射到集合名称。该类com.bigbank.SavingsAccount映射到savingsAccount集合名称。
- 所有嵌套对象都作为嵌套对象存储在文档中,而不是作为 DBRef 存储。
- 转换器使用任何注册的 Spring 转换器来覆盖对象属性到文档字段和值的默认映射。
- 对象的字段用于在文档中的字段之间进行转换。JavaBean不使用公共属性。
- 如果您有一个非零参数构造函数,其构造函数参数名称与文档的顶级字段名称匹配,则使用该构造函数。否则,将使用零参数构造函数。如果有多个非零参数构造函数,则会抛出异常。
18.2.1._id在映射层中如何处理字段。
MongoDB 要求您有一个_id包含所有文档的字段。如果您不提供,驱动程序将分配一个带有生成值的 ObjectId。“_id”字段可以是除数组以外的任何类型,只要它是唯一的。驱动程序自然支持所有原始类型和日期。使用 时,MappingMongoConverter有一些规则控制 Java 类中的属性如何映射到此_id字段。
以下概述了将映射到_id文档字段的字段:
- 带有@Id( org.springframework.data.annotation.Id)注释的字段将映射到该_id字段。
- 没有注释但已命名id的_id字段将映射到该字段。
- 标识符的默认字段名称是_id并且可以通过@Field注释进行自定义。
下面概述了对映射到 _id 文档字段的属性进行的类型转换(如果有)。
- 如果id在 Java 类中将命名字段声明为 String 或 BigInteger,则将尽可能将其转换为 ObjectId 并存储为 ObjectId。ObjectId 作为字段类型也是有效的。如果您id在应用程序中指定了一个值,那么 MongoDB 驱动程序会检测到 ObjectId 的转换。如果指定的id值无法转换为 ObjectId,则该值将按原样存储在文档的 _id 字段中。如果该字段用 注释,这也适用@Id。
- 如果一个字段@MongoId在 Java 类中被注释,它将被转换为并存储为使用它的实际类型。除非@MongoId声明所需的字段类型,否则不会发生进一步的转换。
- 如果一个字段@MongoId(FieldType.…)在 Java 类中被注释,它将尝试将值转换为声明的FieldType.
- 如果名为idid 字段的字段未在 Java 类中声明为 String、BigInteger 或 ObjectID,那么您应该在应用程序中为其分配一个值,以便它可以“按原样”存储在文档的 _id 字段中。
- 如果idJava 类中不存在已命名的字段_id,则驱动程序将生成一个隐式文件,但不会映射到 Java 类的属性或字段。
查询和更新时MongoTemplate将使用转换器来处理与上述保存文档规则相对应的Query和Update对象的转换,因此查询中使用的字段名称和类型将能够匹配域类中的内容。
18.3.数据映射和类型转换
本节解释了类型如何映射到 MongoDB 表示和从 MongoDB 表示映射。Spring Data MongoDB 支持所有可以表示为 BSON(MongoDB 的内部文档格式)的类型。除了这些类型之外,Spring Data MongoDB 还提供了一组内置转换器来映射其他类型。您可以提供自己的转换器来调整类型转换。有关更多详细信息,请参阅[ mapping-explicit-converters]。
下面提供了每种可用类型转换的示例:
18.4.映射配置
除非明确配置,否则MappingMongoConverter在创建MongoTemplate. 您可以创建自己的MappingMongoConverter. 这样做可以让您指定在类路径中可以找到域类的位置,以便 Spring Data MongoDB 可以提取元数据并构建索引。此外,通过创建您自己的实例,您可以注册 Spring 转换器以将特定类映射到数据库或从数据库映射。
您可以使用基于 Java 或基于 XML 的元数据来配置MappingMongoConverter以及 com.mongodb.client.MongoClientMongoTemplate。以下示例使用 Spring 的基于 Java 的配置:
Example 180.@Configuration 类来配置 MongoDB 映射支持
代码语言:javascript复制@Configuration
public class MongoConfig extends AbstractMongoClientConfiguration {
@Override
public String getDatabaseName() {
return "database";
}
// the following are optional
@Override
public String getMappingBasePackage() {
return "com.bigbank.domain";
}
@Override
void configureConverters(MongoConverterConfigurationAdapter adapter) {
adapter.registerConverter(new org.springframework.data.mongodb.test.PersonReadConverter());
adapter.registerConverter(new org.springframework.data.mongodb.test.PersonWriteConverter());
}
@Bean
public LoggingEventListener<MongoMappingEvent> mappingEventsListener() {
return new LoggingEventListener<MongoMappingEvent>();
}
}
映射基础包定义了用于扫描用于预初始化MappingContext. 默认情况下使用配置类包。
为特定域类型配置额外的自定义转换器,用您的自定义实现替换这些类型的默认映射过程。
AbstractMongoClientConfiguration要求您实现定义 acom.mongodb.client.MongoClient以及提供数据库名称的方法。AbstractMongoClientConfiguration还有一个名为的方法getMappingBasePackage(…),您可以重写该方法以告诉转换器在哪里扫描使用@Document注释注释的类。
您可以通过覆盖该 customConversionsConfiguration方法向转换器添加其他转换器。MongoDB 的原生 JSR-310 支持可以通过MongoConverterConfigurationAdapter.useNativeDriverJavaTimeCodecs(). 前面的示例中还显示了一个LoggingEventListener,它记录MongoMappingEvent了发布到 SpringApplicationContextEvent基础设施上的实例。
AbstractMongoClientConfiguration创建一个MongoTemplate实例并将其注册到名称为 的容器中mongoTemplate。
Spring 的 MongoDB 命名空间允许您在 XML 中启用映射功能,如以下示例所示:
示例 181.配置 MongoDB 映射支持的 XML 模式
代码语言:javascript复制<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:mongo="http://www.springframework.org/schema/data/mongo"
xsi:schemaLocation="
http://www.springframework.org/schema/data/mongo https://www.springframework.org/schema/data/mongo/spring-mongo.xsd
http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
<!-- Default bean name is 'mongo' -->
<mongo:mongo-client host="localhost" port="27017"/>
<mongo:db-factory dbname="database" mongo-ref="mongoClient"/>
<!-- by default look for a Mongo object named 'mongo' - default name used for the converter is 'mappingConverter' -->
<mongo:mapping-converter base-package="com.bigbank.domain">
<mongo:custom-converters>
<mongo:converter ref="readConverter"/>
<mongo:converter>
<bean class="org.springframework.data.mongodb.test.PersonWriteConverter"/>
</mongo:converter>
</mongo:custom-converters>
</mongo:mapping-converter>
<bean id="readConverter" class="org.springframework.data.mongodb.test.PersonReadConverter"/>
<!-- set the mapping converter to be used by the MongoTemplate -->
<bean id="mongoTemplate" class="org.springframework.data.mongodb.core.MongoTemplate">
<constructor-arg name="mongoDbFactory" ref="mongoDbFactory"/>
<constructor-arg name="mongoConverter" ref="mappingConverter"/>
</bean>
<bean class="org.springframework.data.mongodb.core.mapping.event.LoggingEventListener"/>
</beans>
该base-package属性告诉它在哪里扫描用@ org.springframework.data.mongodb.core.mapping.Document注释注释的类。
18.5.基于元数据的映射
要充分利用 Spring Data MongoDB 支持中的对象映射功能,您应该使用注释对映射对象进行@Document注释。尽管映射框架没有必要具有此注释(您的 POJO 已正确映射,即使没有任何注释),但它允许类路径扫描器查找和预处理您的域对象以提取必要的元数据。如果你不使用这个注解,你的应用程序在你第一次存储域对象时会受到轻微的性能影响,因为映射框架需要建立它的内部元数据模型,以便它知道你的域对象的属性以及如何坚持他们。以下示例显示了一个域对象:
示例 182. 示例域对象
代码语言:javascript复制package com.mycompany.domain;
@Document
public class Person {
@Id
private ObjectId id;
@Indexed
private Integer ssn;
private String firstName;
@Indexed
private String lastName;
}
该@Id注解告诉你要使用MongoDB的哪个属性映射器_id属性和@Indexed注解告诉映射框架调用createIndex(…)你的文档的那个属性,使得搜索速度更快。自动索引创建仅适用于用@Document.
默认情况下禁用 自动索引创建,需要通过配置启用(请参阅索引创建)。
18.5.1.索引创建
Spring Data MongoDB 可以自动为使用@Document. 自 3.0 版起,必须显式启用索引创建,以防止对集合生命周期和性能影响产生不良影响。在应用程序启动时以及在应用程序运行时第一次访问实体类型时,会为初始实体集自动创建索引。
我们通常建议为基于应用程序的索引控制显式创建索引,因为 Spring Data 无法为在应用程序运行时重新创建的集合自动创建索引。
IndexResolver如果您想使用@Indexed诸如@GeoSpatialIndexed, @TextIndexed,之类的注释,则为程序化索引定义创建提供抽象@CompoundIndex。您可以使用索引定义IndexOperations来创建索引。创建索引的一个好时机是在应用程序启动时,特别是在应用程序上下文刷新之后,由观察触发ContextRefreshedEvent。此事件保证上下文已完全初始化。请注意,此时其他组件,尤其是 bean 工厂可能可以访问 MongoDB 数据库。
示例 183. 单一域类型的程序化索引创建
代码语言:javascript复制class MyListener {
@EventListener(ContextRefreshedEvent.class)
public void initIndicesAfterStartup() {
MappingContext<? extends MongoPersistentEntity<?>, MongoPersistentProperty> mappingContext = mongoTemplate
.getConverter().getMappingContext();
IndexResolver resolver = new MongoPersistentEntityIndexResolver(mappingContext);
IndexOperations indexOps = mongoTemplate.indexOps(DomainType.class);
resolver.resolveIndexFor(DomainType.class).forEach(indexOps::ensureIndex);
}
}
示例 184. 为所有初始实体创建程序索引
代码语言:javascript复制class MyListener{
@EventListener(ContextRefreshedEvent.class)
public void initIndicesAfterStartup() {
MappingContext<? extends MongoPersistentEntity<?>, MongoPersistentProperty> mappingContext = mongoTemplate
.getConverter().getMappingContext();
// consider only entities that are annotated with @Document
mappingContext.getPersistentEntities()
.stream()
.filter(it -> it.isAnnotationPresent(Document.class))
.forEach(it -> {
IndexOperations indexOps = mongoTemplate.indexOps(it.getType());
resolver.resolveIndexFor(it.getType()).forEach(indexOps::ensureIndex);
});
}
}
或者,如果您想在任何组件能够从您的应用程序访问您的数据库之前确保索引和集合存在,请在返回对象之前声明一个@Bean方法MongoTemplate并包含上面的代码MongoTemplate。
要关闭自动创建索引ON请覆盖autoIndexCreation()在你的配置。
@Configuration public class Config extends AbstractMongoClientConfiguration { @Override public boolean autoIndexCreation() { return true; } // ... }
自3.0 版起,自动索引创建默认关闭。
18.5.2.映射注释概述
MappingMongoConverter 可以使用元数据来驱动对象到文档的映射。以下注释可用:
- @Id:应用于字段级别以标记用于标识目的的字段。
- @MongoId:应用于字段级别以标记用于标识目的的字段。接受一个可选FieldType的自定义 id 转换。
- @Document: 应用于类级别,表示该类是映射到数据库的候选。您可以指定将存储数据的集合的名称。
- @DBRef:应用于该字段以指示将使用 com.mongodb.DBRef 存储它。
- @DocumentReference: 应用于该字段以指示它将被存储为指向另一个文档的指针。这可以是单个值(默认为id),也可以是Document通过转换器提供的值。
- @Indexed: 应用于字段级别,描述如何索引字段。
- @CompoundIndex (可重复):在类型级别应用以声明复合索引。
- @GeoSpatialIndexed:应用于字段级别以描述如何对字段进行地理索引。
- @TextIndexed: 在字段级别应用,用于标记要包含在文本索引中的字段。
- @HashIndexed:在字段级别应用以在散列索引中使用以跨分片集群对数据进行分区。
- @Language: 在字段级别应用以设置文本索引的语言覆盖属性。
- @Transient: 默认情况下,所有字段都映射到文档。此注释将应用它的字段排除在数据库中。瞬态属性不能在持久性构造函数中使用,因为转换器无法实现构造函数参数的值。
- @PersistenceConstructor: 标记给定的构造函数 - 即使是受包保护的构造函数 - 在从数据库实例化对象时使用。构造函数参数按名称映射到检索到的文档中的键值。
- @Value:这个注解是 Spring Framework 的一部分。在映射框架内,它可以应用于构造函数参数。这使您可以使用 Spring 表达式语言语句来转换在数据库中检索到的键值,然后再使用它来构造域对象。为了引用给定文档的属性,必须使用如下表达式:@Value("#root.myProperty")whereroot指的是给定文档的根。
- @Field:应用于字段级别,它允许描述字段的名称和类型,因为它将在 MongoDB BSON 文档中表示,从而允许名称和类型与类的字段名称以及属性类型不同。
- @Version:应用于字段级别用于乐观锁定并检查保存操作的修改。初始值是zero(one对于原始类型),它会在每次更新时自动触发。
映射元数据基础设施在一个独立的 spring-data-commons 项目中定义,该项目与技术无关。MongoDB 支持中使用特定子类来支持基于注释的元数据。如果有需求,也可以采取其他策略。
这是一个更复杂的映射示例。
代码语言:javascript复制@Document
@CompoundIndex(name = "age_idx", def = "{'lastName': 1, 'age': -1}")
public class Person<T extends Address> {
@Id
private String id;
@Indexed(unique = true)
private Integer ssn;
@Field("fName")
private String firstName;
@Indexed
private String lastName;
private Integer age;
@Transient
private Integer accountTotal;
@DBRef
private List<Account> accounts;
private T address;
public Person(Integer ssn) {
this.ssn = ssn;
}
@PersistenceConstructor
public Person(Integer ssn, String firstName, String lastName, Integer age, T address) {
this.ssn = ssn;
this.firstName = firstName;
this.lastName = lastName;
this.age = age;
this.address = address;
}
public String getId() {
return id;
}
// no setter for Id. (getter is only exposed for some unit testing)
public Integer getSsn() {
return ssn;
}
// other getters/setters omitted
}
@Field(targetType=…)当映射基础设施推断的本机 MongoDB 类型与预期的类型不匹配时,可以派上用场。就像 for BigDecimal,它被表示为String而不是Decimal128,只是因为早期版本的 MongoDB Server 不支持它。
public class Balance {
@Field(targetType = DECIMAL128)
private BigDecimal value;
// ...
}
您甚至可以考虑自己的自定义注释。
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Field(targetType = FieldType.DECIMAL128)
public @interface Decimal128 { }
// ...
public class Balance {
@Decimal128
private BigDecimal value;
// ...
}
18.5.3.自定义对象构建
映射子系统允许通过使用注释对构造函数进行注释来定制对象构造@PersistenceConstructor。用于构造函数参数的值按以下方式解析:
- 如果参数用注释进行@Value注释,则计算给定的表达式并将结果用作参数值。
- 如果 Java 类型具有名称与输入文档的给定字段匹配的属性,则使用它的属性信息选择适当的构造函数参数以将输入字段值传递给。这仅在 java.class文件中存在参数名称信息时才有效,这可以通过使用调试信息编译源代码或使用-parametersJava 8 中 javac的新命令行开关来实现。
- 否则MappingException将抛出 a 指示无法绑定给定的构造函数参数。
class OrderItem {
private @Id String id;
private int quantity;
private double unitPrice;
OrderItem(String id, @Value("#root.qty ?: 0") int quantity, double unitPrice) {
this.id = id;
this.quantity = quantity;
this.unitPrice = unitPrice;
}
// getters/setters ommitted
}
Document input = new Document("id", "4711");
input.put("unitPrice", 2.5);
input.put("qty",5);
OrderItem item = converter.read(OrderItem.class, input);
如果无法解析给定的属性路径@Value,则quantity参数注释中的 SpEL 表达式将回退到该值0。
@PersistenceConstructor可以在 MappingMongoConverterUnitTests测试套件中找到使用注释的其他示例。
18.5.4. 复合索引
还支持复合索引。它们是在类级别而不是在单个属性上定义的。
复合索引对于提高涉及多个字段条件的查询的性能非常重要
这是一个lastName以升序和age降序创建复合索引的示例:
示例 185. 示例复合索引用法
代码语言:javascript复制package com.mycompany.domain;
@Document
@CompoundIndex(name = "age_idx", def = "{'lastName': 1, 'age': -1}")
public class Person {
@Id
private ObjectId id;
private Integer age;
private String firstName;
private String lastName;
}
@CompoundIndex可重复使用@CompoundIndexes作为其容器。
代码语言:javascript复制@Document
@CompoundIndex(name = "cmp-idx-one", def = "{'firstname': 1, 'lastname': -1}")
@CompoundIndex(name = "cmp-idx-two", def = "{'address.city': -1, 'address.street': 1}")
public class Person {
String firstname;
String lastname;
Address address;
// ...
}
18.5.5.哈希索引
散列索引允许在分片集群中进行基于散列的分片。使用散列字段值对集合进行分片会导致更随机的分布。有关详细信息,请参阅MongoDB 文档。
下面是一个创建哈希索引的示例_id:
示例 186. 哈希索引使用示例
代码语言:javascript复制@Document
public class DomainType {
@HashIndexed @Id String id;
// ...
}
可以在其他索引定义旁边创建散列索引,如下所示,在这种情况下,两个索引都被创建:
示例 187. 哈希索引与简单索引一起使用的示例
代码语言:javascript复制@Document
public class DomainType {
@Indexed
@HashIndexed
String value;
// ...
}
如果上面的例子过于冗长,复合注解允许减少需要在属性上声明的注解数量:
示例 188. 组合哈希索引使用示例
代码语言:javascript复制@Document
public class DomainType {
@IndexAndHash(name = "idx...")
String value;
// ...
}
@Indexed
@HashIndexed
@Retention(RetentionPolicy.RUNTIME)
public @interface IndexAndHash {
@AliasFor(annotation = Indexed.class, attribute = "name")
String name() default "";
}
可能为元注释的某些属性注册别名。
尽管通过注释创建索引在许多场景中派上用场,但考虑到通过手动设置索引来接管更多控制权IndexOperations。
mongoOperations.indexOpsFor(Jedi.class) .ensureIndex(HashedIndex.hashed("useTheForce"));