【Spring连载】使用Spring Data访问 MongoDB(二)----Template API
MongoTemplate及其反应式(reactive)对应类位于org.springframework.data.mongodb.core包中,是Spring MongoDB支持的中心类,为与数据库交互提供了丰富的功能集。该template提供了创建、更新、删除和查询MongoDB文档的方便操作,并提供了域对象和MongoDB documents之间的映射。
配置后,MongoTemplate是线程安全的,可以在多个实例中重用。
一、方便的方法
MongoTemplate类实现了MongoOperations接口。在尽可能多的情况下,MongoOperations上的方法以MongoDB driver Collection对象上可用的方法命名,以便让现有使用driver API的MongoDB开发人员熟悉。例如,你可以找到find, findAndModify, findAndReplace, findOne, insert, remove, save, update, 和 updateMulti等方法。设计目标是尽可能容易地在使用基础MongoDB driver和MongoOperations之间进行转换。这两个API之间的主要区别在于,MongoOperations可以传递域对象,而不是Document。此外,MongoOperations为Query, Criteria, 和 Update操作提供了流畅的API,而不是填充Document来指定这些操作的参数。
有关更多信息,请参阅文档的CRUD(章节八、九)部分。
引用MongoTemplate实例上的操作的首选方式是通过其接口MongoOperations。
二、执行回调函数Execute Callbacks
MongoTemplate提供了许多方便的方法来帮助你轻松执行常见任务。但是,如果你需要直接访问MongoDB driver API,可以使用几种Execute回调方法之一。execute回调为你提供了对MongoCollection或MongoDatabase对象的引用。
- < T> T execute (Class<?> entityClass, CollectionCallback< T> action):为指定类的实体集合运行给定的CollectionCallback。
- < T> T execute (String collectionName, CollectionCallback action):在给定名称的集合上运行给定的CollectionCallback。
- < T> T execute (DbCallback action):运行DbCallback,根据需要转换任何异常。Spring Data MongoDB为MongoDB 2.2版引入的聚合(Aggregation)框架提供了支持。
- < T> T execute (String collectionName, DbCallback< T> action):在给定名称的集合上运行DbCallback,根据需要转换任何异常。
- < T> T executeInSession (DbCallback< T> action):在与数据库的同一连接中运行给定的DbCallback,以确保写操作繁重的环境中的一致性,在这里你可能读取你所写的数据。
以下示例使用CollectionCallback返回有关索引的信息:
boolean hasIndex = template.execute("geolocation", collection ->
Streamable.of(collection.listIndexes(org.bson.Document.class))
.stream()
.map(document -> document.get("name"))
.anyMatch("location_2d"::equals)
);
三、Fluent API
作为与MongoDB进行更低级交互的核心组件,MongoTemplate提供了广泛的方法,涵盖了从集合创建、索引创建和CRUD操作到更高级的功能(如Map Reduce和聚合)的需求。你可以为每个方法找到多个重载。其中大多数涉及API的可选的或可为null的部分。
FluentMongoOperations为MongoOperations的常见方法提供了一个更窄的接口,并提供了更易读、更流畅的API。入口点(insert(…), find(…), update(…), 和其他)遵循基于要运行的操作的自然命名模式。从入口点(entry point)来看,API设计为仅提供依赖于上下文的方法,这些方法会导致调用实际MongoOperations对应方的终止方法 — 在以下示例的情况下使用all方法:
List<Jedi> all = template.query(SWCharacter.class) --------1
.inCollection("star-wars") --------2
.as(Jedi.class) --------3
.matching(query(where("jedi").is(true))) --------4
.all();
1. 用于将查询中使用的字段映射到的类型。
2. 如果未在域类型上定义,则要使用的集合名称。
3. 如果未使用原始域类型,则用此结果类型。
4. 查找查询。
使用投影(projections)允许MongoTemplate通过将实际响应限制在投影目标类型所需的字段来优化结果映射。只要查询本身不包含任何字段限制,并且目标类型是closed接口或DTO投影,这一点就适用。
投影不得应用于DBRefs。
你可以通过终止(terminating)方法在检索单个实体和检索多个实体之间切换,作为List或Stream :first(), one(), all(), 或 stream()。
当使用near(NearQuery)编写地理空间查询时,终止(terminating)方法的数量会更改为仅包括对在MongoDB中运行geoNear命令有效的方法(在GeoResults中获取GeoResult实体),如下例所示:
GeoResults<Jedi> results = template.query(SWCharacter.class)
.as(Jedi.class)
.near(alderaan) // NearQuery.near(-73.9667, 40.78).maxDis…
.all();
四、异常转换
Spring框架为各种各样的数据库和映射技术提供了异常转换。这在传统上是JDBC和JPA的功能。Spring对MongoDB的支持通过提供“org.springframework.dao.support.PersistenceExceptionTranslator”接口的实现,将此功能扩展到MongoDB数据库。
映射到与Spring一致的数据访问异常层次结构背后的动机是,你可以编写可移植的描述性异常处理代码,而无需针对MongoDB错误代码进行编码。Spring的所有数据访问异常都是从根DataAccessException类继承的,因此你可以确保在单个try-catch块中捕获所有与数据库相关的异常。请注意,并不是所有由MongoDB driver抛出的异常都继承自MongoException类。内部异常和消息将被保留,因此不会丢失任何信息。
MongoExceptionTranslator执行的一些映射是“com.mongodb.Network”到DataAccessResourceFailureException,以及MongoException错误代码1003、12001、12010、12011和12012到InvalidDataAccessApiUsageException。查看实现以获取有关映射的更多详细信息。
五、域类型映射
MongoDB documents和域类之间的映射是通过委托给MongoConverter接口的实现来完成的。Spring提供了MappingMongoConverter,但是你也可以编写自己的转换器。虽然MappingMongoConverter可以使用额外的元数据来指定对象到documents的映射,但它也可以通过使用一些用于IDs和集合名称映射的约定来转换不包含额外元数据的对象。这些约定以及映射注解的使用将在对象映射一文中进行解释。
六、配置
你可以使用以下配置来创建和注册MongoTemplate的实例,如下例所示:
基于Java的注册MongoClient对象并启用Spring的异常转换支持
@Configuration
class ApplicationConfiguration {
@Bean
MongoClient mongoClient() {
return MongoClients.create("mongodb://localhost:27017");
}
@Bean
MongoOperations mongoTemplate(MongoClient mongoClient) {
return new MongoTemplate(mongoClient, "geospatial");
}
}
基于XML的注册MongoClient对象
<mongo:mongo-client host="localhost" port="27017" />
<bean id="mongoTemplate" class="org.springframework.data.mongodb.core.MongoTemplate">
<constructor-arg ref="mongoClient" />
<constructor-arg name="databaseName" value="geospatial" />
</bean>
MongoTemplate和ReactiveMongoTemplate有几个重载构造函数:
- MongoTemplate(MongoClient mongo, String databaseName):使用MongoClient对象和默认的数据库名称进行操作。
- MongoTemplate(MongoDatabaseFactory mongoDbFactory):采用一个mongoDbFactory对象,该对象封装了MongoClient对象、数据库名称以及用户名和密码。
- MongoTemplate(MongoDatabaseFactory mongoDbFactory, MongoConverter mongoConverter):添加一个MongoConverter用于映射。
在创建MongoTemplate/ReactiveMongoTemplate时,你可能希望设置的其他可选属性包括默认的WriteResultCheckingPolicy、WriteConcern、ReadPreference和下面列出的其他属性。
6.1 默认读取首选项Read Preference
如果没有通过查询定义其他首选项(读取首选项Read Preference),则将使用默认读取首选项用于读取操作。
6.2 WriteResultChecking策略
在开发过程中,如果从任何mongodb操作返回的“com.mongodb.WriteResult”包含错误,那么记日志或抛出异常都很方便。有时候,在开发过程中会忘记这样做,然后最终得到一个看起来运行成功的应用程序,而事实上,数据库并没有根据你的期望进行修改。你可以将MongoTemplate的WriteResultChecking属性设置为以下值之一:EXCEPTION或NONE,分别抛出Exception或不执行任何操作。默认情况是使用WriteResultChecking值NONE。
6.3 默认写安全WriteConcern
如果尚未通过更高级别的驱动程序(如com.mongodb.client.MongoClient)指定,则可以设置MongoTemplate用于写入操作的“com.mongoodb.WriteConcern”属性。如果未设置WriteConcern属性,则默认为MongoDB driver的DB或Collection中设置的属性。
6.4 WriteConcernResolver
对于更高级的情况,你希望在每个操作的基础上设置不同的WriteConcern值(用于remove, update, insert, 和 save操作),可以在MongoTemplate上配置一个名为WriteConcernResolver的策略接口。由于MongoTemplate用于持久化POJO,WriteConcernResolver允许您创建一个策略,该策略可以将特定的POJO类映射到WriteConcern值。以下列表展示了WriteConcernResolver接口:
public interface WriteConcernResolver {
WriteConcern resolve(MongoAction action);
}
你可以使用MongoAction参数来确定WriteConcern值,也可以使用Template本身的值作为默认值。MongoAction包含要写入的集合名称、POJO的java.lang.Class、转换后的Document、操作(REMOVE、UPDATE、INSERT、INSERT_LIST或SAVE)以及其他一些上下文信息。以下示例展示了两组获得不同WriteConcern设置的类:
public class MyAppWriteConcernResolver implements WriteConcernResolver {
@Override
public WriteConcern resolve(MongoAction action) {
if (action.getEntityType().getSimpleName().contains("Audit")) {
return WriteConcern.ACKNOWLEDGED;
} else if (action.getEntityType().getSimpleName().contains("Metadata")) {
return WriteConcern.JOURNALED;
}
return action.getDefaultWriteConcern();
}
}
6.5 发布实体生命周期事件
template发布生命周期事件。在没有监听器的情况下,可以禁用此功能。
@Bean
MongoOperations mongoTemplate(MongoClient mongoClient) {
MongoTemplate template = new MongoTemplate(mongoClient, "geospatial");
template.setEntityLifecycleEventsEnabled(false);
// ...
}
6.6 配置EntityCallbacks
在生命周期事件后,template调用EntityCallbacks,可以通过template API设置(如果没有自动配置)。
@Bean
MongoOperations mongoTemplate(MongoClient mongoClient) {
MongoTemplate template = new MongoTemplate(mongoClient, "...");
template.setEntityCallbacks(EntityCallbacks.create(...));
// ...
}
6.7 Document count配置
通过将MongoTemplate#useEstimatedCount(…)设置为true,使用空过滤器查询的MongoTemplate#count(…)操作将被委托给estimatedCount,只要没有活动的事务并且template没有绑定到session。有关更多信息,请参阅章节十Counting Documents部分。
七、索引及集合管理
MongoTemplate和ReactiveMongoTemplate提供了管理索引和集合的方法。这些方法被收集到一个叫做IndexOperations的helper接口中,对应reactive的接口是ReactiveIndexOperations。可以通过调用indexOps方法并传入集合名称或实体的java.lang.Class(集合名称派生自.class,可以通过名称派生,也可以通过注解元数据派生)来访问这些操作。下面的清单展示了IndexOperations接口:
public interface IndexOperations {
String ensureIndex(IndexDefinition indexDefinition);
void alterIndex(String name, IndexOptions options);
void dropIndex(String name);
void dropAllIndexes();
List<IndexInfo> getIndexInfo();
}
7.1 创建索引的方法
你可以在一个集合上创建一个索引,通过使用MongoTemplate类来提高查询性能,如下所示:
template.indexOps(Person.class)
.ensureIndex(new Index().on("name",Order.ASCENDING));
ensureIndex确保提供的IndexDefinition的索引存在于集合中。你可以通过使用IndexDefinition、GeoSpatialIndex和TextIndexDefinition类来创建标准、地理空间和文本索引。例如,给定在如下定义的Venue类,你可以声明一个地理空间查询,如下面的示例所示:
@Document(collection="newyork")
public class Venue {
@Id
private String id;
private String name;
private double[] location;
@PersistenceConstructor
Venue(String name, double[] location) {
super();
this.name = name;
this.location = location;
}
public Venue(String name, double x, double y) {
super();
this.name = name;
this.location = new double[] { x, y };
}
public String getName() {
return name;
}
public double[] getLocation() {
return location;
}
@Override
public String toString() {
return "Venue [id=" + id + ", name=" + name + ", location="
+ Arrays.toString(location) + "]";
}
}
template.indexOps(Venue.class)
.ensureIndex(new GeospatialIndex("location"));
Index和GeospatialIndex支持collations的配置。
7.2 访问索引信息
IndexOperations接口具有getIndexInfo方法,该方法返回IndexInfo对象列表。此列表包含在集合上定义的所有索引。下面的例子在Person类上定义了一个索引,它有一个age属性:
template.indexOps(Person.class)
.ensureIndex(new Index().on("age", Order.DESCENDING).unique());
List<IndexInfo> indexInfoList = template.indexOps(Person.class)
.getIndexInfo();
7.3 操作集合的方法
下面的例子展示了如何创建一个集合:
MongoCollection<Document> collection = null;
if (!template.getCollectionNames().contains("MyNewCollection")) {
collection = mongoTemplate.createCollection("MyNewCollection");
}
集合创建允许使用CollectionOptions进行自定义,并支持collations。
与MongoCollections交互的方法:
- getCollectionNames:返回一组集合名称。
- collectionExists:检查给定名称的集合是否存在。
- createCollection:创建一个未封顶(uncapped)的集合。
- dropCollection:删除集合。
- getCollection:按名称获取集合,如果不存在则创建它。
7.4 时间序列Time Series
MongoDB 5.0引入了时间序列集合,这些集合经过优化,可以随着时间的推移有效地存储文档,如度量(measurements)或事件。这些集合需要在插入任何数据之前创建。集合可以通过运行createCollection命令、定义时间序列集合选项或从@TimeSeries注解中提取选项来创建,如下例所示。
例1:创建时间序列集合
通过MongoDB Driver创建时间序列
template.execute(db -> {
com.mongodb.client.model.CreateCollectionOptions options = new CreateCollectionOptions();
options.timeSeriesOptions(new TimeSeriesOptions("timestamp"));
db.createCollection("weather", options);
return "OK";
});
用CollectionOptions创建一个时间序列集合
template.createCollection("weather", CollectionOptions.timeSeries("timestamp"));
创建从注解派生的时间序列集合
@TimeSeries(collection="weather", timeField = "timestamp")
public class Measurement {
String id;
Instant timestamp;
// ...
}
template.createCollection(Measurement.class);
上面的代码片段可以很容易地转移到提供相同方法的响应式(reactive)API中。确保正确订阅返回的publishers。
八、Counting Documents
template API提供了各种方法来计算符合给定标准的documents数量。其中之一概述如下。
template.query(Person.class)
.matching(query(where("firstname").is("luke")))
.count();
在SpringData MongoDB的3.x之前版本中,计数操作使用MongoDB的内部集合统计信息。随着MongoDB事务的引入,这不再可能,因为统计数据无法正确反映需要基于聚合的计数方法的事务过程中的潜在变化。因此,在2.x版本中,如果没有事务正在进行,MongoOperations.count()将使用集合统计信息,如果有事务,则使用聚合变量。
从Spring Data MongoDB 3.x开始,任何计数操作都会通过MongoDB的countDocuments使用基于聚合的计数方法,而不管是否存在过滤条件。如果应用程序可以接受处理集合统计信息的限制,那么MongoOperations.estimatedCount()提供了另一种选择。
通过将MongoTemplate#useEstimatedCount(…)设置为true,只要没有活动的事务并且template没有绑定到session,使用空过滤器查询的MongoTemplate#count(…) 操作将被委托给estimatedCount。仍然可以通过MongoTemplate#exactCount获得确切的数字,但可能会加快速度。
MongoDB的原生countDocuments方法和$ match聚合不支持$ near和$ nearSphere,但需要$ geoWithin以及不支持$ minDistance的$ center或$centerSphere(请参阅jira.mongodb.org/browse/SERVER-37043)。
因此,将使用Reactive-/MongoTemplate为计数操作重写给定的Query,以绕过如下所示的问题。
{ location : { $near : [-73.99171, 40.738868], $maxDistance : 1.1 } } --------1
{ location : { $geoWithin : { $center: [ [-73.99171, 40.738868], 1.1] } } } --------2
{ location : { $near : [-73.99171, 40.738868], $minDistance : 0.1, $maxDistance : 1.1 } } --------3
{$and :[ { $nor :[ { location :{ $geoWithin :{ $center :[ [-73.99171, 40.738868 ], 0.01] } } } ]}, { location :{ $geoWithin :{ $center :[ [-73.99171, 40.738868 ], 1.1] } } } ] } --------4
1. 使用$near计数源查询。
2. 现在使用$geoWithin和$center重写查询。
3. 使用$near、$minDistance和$maxDistance计算源查询。
4. 现在重写查询,使用$nor $geowithin条件的组合,以解决不支持的$minDistance。
723

被折叠的 条评论
为什么被折叠?



