Spring Data (数据)MongoDB(三)

时间:2022-11-21 18:15:39

Spring Data (数据)MongoDB(三)

10.21. 更改流

从MongoDB 3.6开始,Change Streams允许应用程序获得有关更改的通知,而无需跟踪oplog。

更改流支持仅适用于副本集或分片集群。

更改流可以与命令式和反应式MongoDB Java驱动程序一起使用。强烈建议使用反应式变体,因为它的资源密集度较低。但是,如果您无法使用反应式 API,您仍然可以使用 Spring 生态系统中已经流行的消息传递概念来获取更改事件。

可以在集合和数据库级别同时监视,而数据库级别变体发布 数据库中所有集合的更改。订阅数据库更改流时,请确保使用 事件类型的合适类型,因为转换可能无法正确应用于不同的实体类型。 有疑问,请使用。​​Document​

10.21.1. 使用 更改流​​MessageListener​

使用同步驱动程序侦听更改流会创建一个长时间运行的阻止任务,需要将其委派给单独的组件。 在这种情况下,我们需要首先创建一个,这将是运行特定任务的主要入口点。 Spring Data MongoDB已经附带了一个默认的实现,该实现可以在其上运行,并且能够为论坛创建和运行实例。​​MessageListenerContainer​​​​SubscriptionRequest​​​​MongoTemplate​​​​Task​​​​ChangeStreamRequest​

以下示例演示如何将更改流与实例一起使用:​​MessageListener​

例 116.使用实例更改流​​MessageListener​

MessageListenerContainer container = new DefaultMessageListenerContainer(template);
container.start();

MessageListener<ChangeStreamDocument<Document>, User> listener = System.out::println;
ChangeStreamRequestOptions options = new ChangeStreamRequestOptions("user", ChangeStreamOptions.empty());

Subscription subscription = container.register(new ChangeStreamRequest<>(listener, options), User.class);

// ...

container.stop();

启动容器将初始化资源并启动已注册实例的实例。启动后添加的请求将立即运行。​​Task​​​​SubscriptionRequest​

定义收到 ais 时调用的侦听器。将转换为请求的域类型。用于接收未经转换的原始结果。​​Message​​​​Message#getBody()​​​​Document​

设置要收听的集合并提供其他选项。​​ChangeStreamOptions​

注册请求。返回的可用于检查当前状态并取消它以释放资源。​​Subscription​​​​Task​

一旦确定不再需要容器,请不要忘记停止它。这样做会停止容器内所有正在运行的实例。​​Task​


处理时的错误将传递给 。如果未另行说明,则默认应用日志追加。
请使用以提供其他功能。​​​org.springframework.util.ErrorHandler​​​​ErrorHandler​​​​register(request, body, errorHandler)​


10.21.2. 反应性变更流

使用反应式 API 订阅更改流是处理流的更自然的方法。尽管如此,诸如此类的基本构建块保持不变。以下示例演示如何使用更改流发射器:​​ChangeStreamOptions​​​​ChangeStreamEvent​

例 117.更改流发射​​ChangeStreamEvent​

Flux<ChangeStreamEvent<User>> flux = reactiveTemplate.changeStream(User.class) 
.watchCollection("people")
.filter(where("age").gte(38))
.listen();

基础文档应转换为的事件目标类型。省略这一点以接收原始结果而无需转换。

使用聚合管道或仅使用查询来筛选事件。​​Criteria​

Obtain a of change stream events. The is converted to the requested domain type from (2).​​Flux​​​​ChangeStreamEvent#getBody()​

10.21.3. Resuming Change Streams

Change Streams can be resumed and resume emitting events where you left. To resume the stream, you need to supply either a resume token or the last known server time (in UTC). Use to set the value accordingly.​​ChangeStreamOptions​

The following example shows how to set the resume offset using server time:

Example 118. Resume a Change Stream

Flux<ChangeStreamEvent<User>> resumed = template.changeStream(User.class)
.watchCollection("people")
.resumeAt(Instant.now().minusSeconds(1))
.listen();

You may obtain the server time of an through the method or use the exposed through .​​ChangeStreamEvent​​​​getTimestamp​​​​resumeToken​​​​getResumeToken​

在某些情况下,在恢复更改流时,可能不是一个足够精确的度量。为此,请使用MongoDB原生的BsonTimestamp​。​​Instant​

10.22. 时间序列

MongoDB 5.0引入了时间序列集合,这些集合经过优化,可以有效地存储一段时间内的文档,例如测量或事件。 在插入任何数据之前,需要这样创建这些集合。 可以通过运行命令、定义时序集合选项或从注释中提取选项来创建集合,如以下示例所示。​​createCollection​​​​@TimeSeries​

例 119.创建时序集合

通过MongoDB驱动程序创建时间序列

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);

上面的代码片段可以很容易地转移到提供相同方法的反应式 API。 请确保正确订阅返回的发布者。

10.23. 可观测性

Spring Data MongoDB目前拥有最新的代码来支持MongoDB应用程序中的可观测性。 然而,Spring Boot(尚未)接受这些更改。 在应用这些更改之前,如果您希望使用Spring Data MongoDB的可观测性风格,则必须执行以下步骤。

  1. 首先,您必须通过自定义您的类或您的配置类之一来选择加入Spring Data MongoDB的配置设置。MongoClientSettings@SpringBootApplication

    例 120。注册 MongoDB 千分尺定制器设置

@Bean
MongoClientSettingsBuilderCustomizer mongoMetricsSynchronousContextProvider(ObservationRegistry registry) {
return (clientSettingsBuilder) -> {
clientSettingsBuilder.contextProvider(ContextProviderFactory.create(registry))
.addCommandListener(new MongoObservationCommandListener(registry));
};
}
  1. 您的项目必须包括弹簧启动执行器
  2. 禁用 Spring Boot 自动配置的 MongoDB 命令侦听器,并通过将以下属性添加到您的application.properties

    例 121.要应用的自定义设置

# Disable Spring Boot's autoconfigured tracing
management.metrics.mongo.command.enabled=false
# Enable it manually
management.tracing.enabled=true

请务必根据千分尺的参考文档添加配置正在使用的示踪剂所需的任何其他相关设置。

这应该可以!您现在正在使用Spring Data MongoDB对Spring Observability的API的使用。​​Observation​

reference/observability.adoc 中未解析的指令 - include::../../../../target/_conventions.adoc[]

reference/observability.adoc 中未解析的指令 - include::../../../../target/_metrics.adoc[]

reference/observability.adoc 中未解析的指令 - include::../../../../target/_spans.adoc[]

另请参阅开放遥测语义约定以获取进一步参考。

11. MongoDB 会话

从3.6版本开始,MongoDB支持会话的概念。会话的使用启用了MongoDB的因果一致性模型,该模型保证以尊重其因果关系的顺序运行操作。这些被拆分为实例和实例。在本节中,当我们谈到会话时,我们指的是。​​ServerSession​​​​ClientSession​​​​ClientSession​

客户端会话中的操作不与会话外部的操作隔离。

Bothand提供了网关方法,用于绑定a到操作.并使用实现MongoDB的集合和数据库接口的会话代理对象,因此您无需在每次调用时添加会话。这意味着潜在的调用 to 被委派给。​​MongoOperations​​​​ReactiveMongoOperations​​​​ClientSession​​​​MongoCollection​​​​MongoDatabase​​​​MongoCollection#find()​​​​MongoCollection#find(ClientSession)​

诸如返回本机MongoDB Java驱动程序网关对象之类的方法,这些对象本身提供了专用方法。这些方法不是会话代理的。在直接与 aorand 交互时,您应该提供所需的位置,而不是通过其中一个回调。​​(Reactive)MongoOperations#getCollection​​​​MongoCollection​​​​ClientSession​​​​ClientSession​​​​MongoCollection​​​​MongoDatabase​​​​#execute​​​​MongoOperations​

11.1. 同步支持。​​ClientSession​

以下示例显示了会话的使用情况:

例 122.with​​ClientSession​​​​MongoOperations​

ClientSessionOptions sessionOptions = ClientSessionOptions.builder()
.causallyConsistent(true)
.build();

ClientSession session = client.startSession(sessionOptions);

template.withSession(() -> session)
.execute(action -> {

Query query = query(where("name").is("Durzo Blint"));
Person durzo = action.findOne(query, Person.class);

Person azoth = new Person("Kylar Stern");
azoth.setMaster(durzo);

action.insert(azoth);

return azoth;
});

session.close()


从服务器获取新会话。


像以前一样使用方法。自动应用。​​MongoOperation​​​​ClientSession​


确保关闭。​​ClientSession​


关闭会话。


在处理实例时,尤其是延迟加载的实例时,在加载所有数据之前不要关闭。否则,延迟提取将失败。​​DBRef​​​​ClientSession​

11.2. 反应式支持​​ClientSession​

响应式对应项使用与命令式相同的构建块,如以下示例所示:

例 123.客户端会话​​ReactiveMongoOperations​

ClientSessionOptions sessionOptions = ClientSessionOptions.builder()
.causallyConsistent(true)
.build();

Publisher<ClientSession> session = client.startSession(sessionOptions);

template.withSession(session)
.execute(action -> {

Query query = query(where("name").is("Durzo Blint"));
return action.findOne(query, Person.class)
.flatMap(durzo -> {

Person azoth = new Person("Kylar Stern");
azoth.setMaster(durzo);

return action.insert(azoth);
});
}, ClientSession::close)
.subscribe();

获取用于新会话检索的 a。​​Publisher​

像以前一样使用方法。这是自动获得和应用的。​​ReactiveMongoOperation​​​​ClientSession​

确保关闭。​​ClientSession​

在您订阅之前,什么都不会发生。有关详细信息,请参阅项目反应器参考指南。

通过使用提供实际会话的 a,您可以将会话获取推迟到实际订阅点。 尽管如此,您仍然需要在完成后关闭会话,以免使用陈旧的会话污染服务器。当您不再需要会话时,使用钩子到呼叫。 如果您希望对会话本身有更多的控制,则可以通过驱动程序获取并通过 a 提供它。​​Publisher​​​​doFinally​​​​execute​​​​ClientSession#close()​​​​ClientSession​​​​Supplier​


反应性使用仅限于模板 API 使用。目前没有与反应式存储库的会话集成。​​ClientSession​

12. 蒙戈数据库事务

从版本4开始,MongoDB支持​​事务​​。事务建立在​​会话​​之上,因此需要活动。​​ClientSession​


除非您在应用程序上下文中指定 a,否则事务支持将被禁用。您可以使用参与正在进行的非本机MongoDB事务。​​MongoTransactionManager​​​​setSessionSynchronization(ALWAYS)​

若要获得对事务的完全编程控制,可能需要启用会话回调。​​MongoOperations​

以下示例显示 a 中的编程事务控制:​​SessionCallback​

例 124.程序化交易

ClientSession session = client.startSession(options);                   

template.withSession(session)
.execute(action -> {

session.startTransaction();

try {

Step step = // ...;
action.insert(step);

process(step);

action.update(Step.class).apply(Update.set("state", // ...

session.commitTransaction();

} catch (RuntimeException e) {
session.abortTransaction();
}
}, ClientSession::close)

获取新的。​​ClientSession​

开始事务。

如果一切按预期进行,请提交更改。

有些东西坏了,所以回滚一切。

完成后不要忘记关闭会话。

前面的示例允许您在回调中使用会话作用域实例时完全控制事务行为,以确保会话传递到每个服务器调用。 为了避免此方法带来的一些开销,您可以使用 ato 消除手动事务流的一些噪音。​​MongoOperations​​​​TransactionTemplate​

12.1. 交易​​TransactionTemplate​

Spring Data MongoDB 事务支持 a。以下示例演示如何创建和使用:​​TransactionTemplate​​​​TransactionTemplate​

例 125。交易​​TransactionTemplate​

template.setSessionSynchronization(ALWAYS);                                     

// ...

TransactionTemplate txTemplate = new TransactionTemplate(anyTxManager);

txTemplate.execute(new TransactionCallbackWithoutResult() {

@Override
protected void doInTransactionWithoutResult(TransactionStatus status) {

Step step = // ...;
template.insert(step);

process(step);

template.update(Step.class).apply(Update.set("state", // ...
};
});

在模板 API 配置期间启用事务同步。

创建使用提供的。​​TransactionTemplate​​​​PlatformTransactionManager​

在回调中,和事务已经注册。​​ClientSession​

更改运行时的状态(如前面清单的第 1 项中可能认为的那样)可能会导致线程和可见性问题。​​MongoTemplate​

12.2. 交易​​MongoTransactionManager​

​MongoTransactionManager​​是通往众所周知的 Spring 事务支持的门户。它允许应用程序使用Spring的托管事务功能。 绑定到线程。检测会话并相应地对这些与事务关联的资源进行操作。还可以参与其他正在进行的事务。以下示例演示如何使用 创建和使用事务:​​MongoTransactionManager​​​​ClientSession​​​​MongoTemplate​​​​MongoTemplate​​​​MongoTransactionManager​

例 126。交易​​MongoTransactionManager​

@Configuration
static class Config extends AbstractMongoClientConfiguration {

@Bean
MongoTransactionManager transactionManager(MongoDatabaseFactory dbFactory) {
return new MongoTransactionManager(dbFactory);
}

// ...
}

@Component
public class StateService {

@Transactional
void someBusinessFunction(Step step) {

template.insert(step);

process(step);

template.update(Step.class).apply(Update.set("state", // ...
};
});

在应用程序上下文中注册。​​MongoTransactionManager​

将方法标记为事务性。

​@Transactional(readOnly = true)​​​建议还启动一个将传出请求添加到传出请求的事务。​​MongoTransactionManager​​​​ClientSession​

12.3. 反应式交易

与反应性支持相同,提供专用的操作方法 在事务中,无需担心根据操作结果提交或停止操作。​​ClientSession​​​​ReactiveMongoTemplate​

在事务流中使用普通的MongoDB反应式驱动程序API可能如下所示。​​delete​

例 127.本机驱动程序支持

Mono<DeleteResult> result = Mono
.from(client.startSession())

.flatMap(session -> {
session.startTransaction();

return Mono.from(collection.deleteMany(session, ...))

.onErrorResume(e -> Mono.from(session.abortTransaction()).then(Mono.error(e)))

.flatMap(val -> Mono.from(session.commitTransaction()).then(Mono.just(val)))

.doFinally(signal -> session.close());
});

首先,我们显然需要启动会话。

一旦我们有了手头,就开始交易。​​ClientSession​

通过传递给操作在事务中操作。​​ClientSession​

如果操作异常完成,我们需要停止事务并保留错误。

或者,当然,在成功的情况下提交更改。仍保留操作结果。

最后,我们需要确保关闭会话。

上述操作的罪魁祸首是保持主流而不是交易结果 通过 Bothor 发布,这会导致设置相当复杂。​​DeleteResult​​​​commitTransaction()​​​​abortTransaction()​

12.4. 交易​​TransactionalOperator​

Spring Data MongoDB 事务支持 a。以下示例演示如何创建和使用:​​TransactionalOperator​​​​TransactionalOperator​

例 128。交易​​TransactionalOperator​

template.setSessionSynchronization(ALWAYS);                                          

// ...

TransactionalOperator rxtx = TransactionalOperator.create(anyTxManager,
new DefaultTransactionDefinition());


Step step = // ...;
template.insert(step);

Mono<Void> process(step)
.then(template.update(Step.class).apply(Update.set("state", …))
.as(rxtx::transactional)
.then();

为事务参与启用事务同步。

创建使用提供的。​​TransactionalOperator​​​​ReactiveTransactionManager​

​TransactionalOperator.transactional(…)​​为所有上游操作提供事务管理。

12.5. 交易​​ReactiveMongoTransactionManager​

​ReactiveMongoTransactionManager​​是通往众所周知的 Spring 事务支持的门户。 它允许应用程序利用Spring的托管事务功能。 绑定到订阅者。检测会话并相应地对这些与事务关联的资源进行操作。还可以参与其他正在进行的事务。 以下示例演示如何使用 创建和使用事务:​​ReactiveMongoTransactionManager​​​​ClientSession​​​​Context​​​​ReactiveMongoTemplate​​​​ReactiveMongoTemplate​​​​ReactiveMongoTransactionManager​

例 129。交易​​ReactiveMongoTransactionManager​

@Configuration
public class Config extends AbstractReactiveMongoConfiguration {

@Bean
ReactiveMongoTransactionManager transactionManager(ReactiveMongoDatabaseFactory factory) {
return new ReactiveMongoTransactionManager(factory);
}

// ...
}

@Service
public class StateService {

@Transactional
Mono<UpdateResult> someBusinessFunction(Step step) {

return template.insert(step)
.then(process(step))
.then(template.update(Step.class).apply(Update.set("state", …));
};
});

在应用程序上下文中注册。​​ReactiveMongoTransactionManager​

将方法标记为事务性。

​@Transactional(readOnly = true)​​​建议还启动一个将传出请求添加到传出请求的事务。​​ReactiveMongoTransactionManager​​​​ClientSession​

12.6. 交易内的特殊行为

在事务内部,MongoDB服务器的行为略有不同。

连接设置

MongoDB 驱动程序提供专用的副本集名称配置选项,将驱动程序纳入自动检测 模式。此选项有助于在事务期间识别主副本集节点和命令路由。

确保添加到MongoDB URI。有关更多详细信息,请参阅连接字符串选项​。​​replicaSet​

收集操作

MongoDB不支持事务中的集合操作,例如集合创建。这也 影响首次使用时发生的动态集合创建。因此,请确保具备所有必需的内容 结构到位。

暂时性错误

MongoDB可以为事务操作期间引发的错误添加特殊标签。这些可能表示暂时性故障 仅重试操作即可消失。 我们强烈建议出于这些目的进行Spring 重试。不过 可以覆盖以实现 MongoDB 参考手册中概述的重试提交操作行为。​​MongoTransactionManager#doCommit(MongoTransactionObject)​

计数

MongoDB对收集统计信息进行操作,这些统计信息可能无法反映事务中的实际情况。 在多文档事务中发出命令时,服务器响应错误 50851。 一旦检测到活动事务,所有公开的方法都会被转换并委托给 聚合框架使用和运算符,保留设置等。​​count​​​​count​​​​MongoTemplate​​​​count()​​​​$match​​​​$count​​​​Query​​​​collation​

在聚合计数帮助程序中使用 geo 命令时,存在限制。不能使用以下运算符,必须替换为其他运算符:

  • ​$where​​ → $expr
  • ​$near​​→$geoWithin$center
  • ​$nearSphere​​→$geoWithin$centerSphere

查询使用和必须重写为尊重。这同样适用于必须更改为的存储库查询方法中的查询关键字。另请参阅 MongoDB JIRA 票证 DRIVERS-518以获取进一步参考。​​Criteria.near(…)​​​​Criteria.nearSphere(…)​​​​Criteria.within(…)​​​​Criteria.withinSphere(…)​​​​near​​​​within​

以下代码片段显示了会话绑定闭包内的用法:​​count​

session.startTransaction();

template.withSession(session)
.execute(action -> {
action.count(query(where("state").is("active")), Step.class)
...

上面的代码片段在以下命令中具体化:

db.collection.aggregate(
[
{ $match: { state: "active" } },
{ $count: "totalEntityCount" }
]
)

而不是:

db.collection.find( { state: "active" } ).count()

13. 响应式 MongoDB 支持

反应式MongoDB支持包含以下基本功能集:

  • 使用基于 Java 的类、实例和副本集的 Spring 配置支持。@ConfigurationMongoClient
  • ​ReactiveMongoTemplate​​,这是一个帮助程序类,它通过以响应式方式使用来提高工作效率。它包括实例和 POJO 之间的集成对象映射。MongoOperationsDocument
  • 异常转换为 Spring 的可移植数据访问异常层次结构。
  • 功能丰富的对象映射与 Spring 集成。ConversionService
  • 可扩展以支持其他元数据格式的基于批注的映射元数据。
  • 持久性和映射生命周期事件。
  • 基于 Java 的、 和 DSL。QueryCriteriaUpdate
  • 自动实现反应式存储库接口,包括对自定义查询方法的支持。

对于大多数任务,您应该使用存储库支持,这两者都使用丰富的映射 functionality.is 查找访问功能(如递增计数器或临时 CRUD 操作)的位置。还提供了回调方法,以便您可以使用低级 API 工件(例如)直接与 MongoDB 通信。各种API工件上的命名约定的目标是在基本的MongoDB Java驱动程序中复制这些约定,以便您可以将现有知识映射到Spring API上。​​ReactiveMongoTemplate​​​​ReactiveMongoTemplate​​​​ReactiveMongoTemplate​​​​MongoDatabase​

13.1. 入门

Spring MongoDB 支持需要 MongoDB 2.6 或更高版本和 Java SE 8 或更高版本。

首先,您需要设置一个正在运行的MongoDB服务器。有关如何启动MongoDB实例的说明,请参阅MongoDB快速入门指南。安装后,启动MongoDB通常是运行以下命令的问题:​​${MONGO_HOME}/bin/mongod​

要在 STS 中创建 Spring 项目,请转到“新建→ Spring 模板项目→文件”→“简单 Spring 实用程序项目”,并在出现提示时按“是”。然后输入项目和包名称,例如 org.spring.mongodb.example。

然后将以下内容添加到 pom.xml依赖项部分。

<dependencies>

<!-- other dependency elements omitted -->

<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-mongodb</artifactId>
<version>4.0.0</version>
</dependency>

<dependency>
<groupId>org.mongodb</groupId>
<artifactId>mongodb-driver-reactivestreams</artifactId>
<version>4.8.0</version>
</dependency>

<dependency>
<groupId>io.projectreactor</groupId>
<artifactId>reactor-core</artifactId>
<version>2022.0.0</version>
</dependency>

</dependencies>

MongoDB使用两种不同的驱动程序进行阻塞和反应(非阻塞)数据访问。虽然默认情况下提供阻止操作,但您可以选择加入反应性使用。

若要开始使用工作示例,请创建一个要保留的简单类,如下所示:​​Person​

@Document
public class Person {

private String id;
private String name;
private int age;

public Person(String name, int age) {
this.name = name;
this.age = age;
}

public String getId() {
return id;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}

@Override
public String toString() {
return "Person [id=" + id + ", name=" + name + ", age=" + age + "]";
}
}

然后创建一个要运行的应用程序,如下所示:

public class ReactiveMongoApp {

private static final Logger log = LoggerFactory.getLogger(ReactiveMongoApp.class);

public static void main(String[] args) throws Exception {

CountDownLatch latch = new CountDownLatch(1);

ReactiveMongoTemplate mongoOps = new ReactiveMongoTemplate(MongoClients.create(), "database");

mongoOps.insert(new Person("Joe", 34))
.flatMap(p -> mongoOps.findOne(new Query(where("name").is("Joe")), Person.class))
.doOnNext(person -> log.info(person.toString()))
.flatMap(person -> mongoOps.dropCollection("person"))
.doOnComplete(latch::countDown)
.subscribe();

latch.await();
}
}

运行前面的类将生成以下输出:

2016-09-20 14:56:57,373 DEBUG .index.MongoPersistentEntityIndexCreator: 124 - Analyzing class class example.ReactiveMongoApp$Person for index information.
2016-09-20 14:56:57,452 DEBUG .data.mongodb.core.ReactiveMongoTemplate: 975 - Inserting Document containing fields: [_class, name, age] in collection: person
2016-09-20 14:56:57,541 DEBUG .data.mongodb.core.ReactiveMongoTemplate:1503 - findOne using query: { "name" : "Joe"} fields: null for class: class example.ReactiveMongoApp$Person in collection: person
2016-09-20 14:56:57,545 DEBUG .data.mongodb.core.ReactiveMongoTemplate:1979 - findOne using query: { "name" : "Joe"} in db.collection: database.person
2016-09-20 14:56:57,567 INFO example.ReactiveMongoApp: 43 - Person [id=57e1321977ac501c68d73104, name=Joe, age=34]
2016-09-20 14:56:57,573 DEBUG .data.mongodb.core.ReactiveMongoTemplate: 528 - Dropped collection [person]

即使在这个简单的示例中,也有几点需要注意:

  • 您可以使用要使用的标准对象和数据库的名称来实例化Spring Mongo(ReactiveMongoTemplate)的中心辅助类。com.mongodb.reactivestreams.client.MongoClient
  • 映射器针对标准 POJO 对象工作,而无需任何其他元数据(尽管您可以选择提供该信息。看这里。
  • 约定用于处理 ID 字段,将其转换为存储在数据库中时。ObjectId
  • 映射约定可以使用字段访问。请注意,该类只有 getter。Person
  • 如果构造函数参数名称与存储文档的字段名称匹配,则它们用于实例化对象

有一个GitHub 存储库,其中包含几个示例,您可以下载并试用这些示例,以了解库的工作原理。

13.2. 使用 Spring 和反应式流驱动程序连接到 MongoDB

使用MongoDB和Spring时的首要任务之一是使用IoC容器创建对象。​​com.mongodb.reactivestreams.client.MongoClient​

13.2.1. 使用基于 Java 的元数据注册 MongoClient 实例

以下示例演示如何使用基于 Java 的 Bean 元数据来注册 的实例:​​com.mongodb.reactivestreams.client.MongoClient​

例 130.使用基于 Java 的 Bean 元数据注册对象​​com.mongodb.reactivestreams.client.MongoClient​

@Configuration
public class AppConfig {

/*
* Use the Reactive Streams Mongo Client API to create a com.mongodb.reactivestreams.client.MongoClient instance.
*/
public @Bean MongoClient reactiveMongoClient() {
return MongoClients.create("mongodb://localhost");
}
}

这种方法允许您使用标准API(您可能已经知道)。​​com.mongodb.reactivestreams.client.MongoClient​

另一种方法是使用 Spring 的实例向容器注册实例。与直接实例化实例相比,该方法还有一个额外的优势,即还为容器提供了一个实现,该实现将MongoDB异常转换为Spring的可移植层次结构中的异常,用于使用注释注释的数据访问类。这种层次结构和用法在Spring 的 DAO 支持功能中有所描述。​​com.mongodb.reactivestreams.client.MongoClient​​​​ReactiveMongoClientFactoryBean​​​​com.mongodb.reactivestreams.client.MongoClient​​​​FactoryBean​​​​ExceptionTranslator​​​​DataAccessException​​​​@Repository​​​​@Repository​

以下示例显示了支持 带注释类的异常转换的基于 Java 的 Bean 元数据:​​@Repository​

例 131.使用 Spring 的 MongoClientFactoryBean 注册对象并启用 Spring 的异常转换支持​​com.mongodb.reactivestreams.client.MongoClient​

@Configuration
public class AppConfig {

/*
* Factory bean that creates the com.mongodb.reactivestreams.client.MongoClient instance
*/
public @Bean ReactiveMongoClientFactoryBean mongoClient() {

ReactiveMongoClientFactoryBean clientFactory = new ReactiveMongoClientFactoryBean();
clientFactory.setHost("localhost");

return clientFactory;
}
}

要访问由其他人或您自己的类创建的对象,请从上下文中获取。​​com.mongodb.reactivestreams.client.MongoClient​​​​ReactiveMongoClientFactoryBean​​​​@Configuration​​​​MongoClient​

13.2.2. 反应式Mongo数据库工厂接口

虽然作为反应式 MongoDB 驱动程序 API 的入口点,但连接到特定的 MongoDB 数据库实例需要其他信息,例如数据库名称。使用该信息,您可以获取对象并访问特定MongoDB数据库实例的所有功能。Spring 提供了引导数据库连接的接口。以下清单显示了界面:​​com.mongodb.reactivestreams.client.MongoClient​​​​com.mongodb.reactivestreams.client.MongoDatabase​​​​org.springframework.data.mongodb.core.ReactiveMongoDatabaseFactory​​​​ReactiveMongoDatabaseFactory​

public interface ReactiveMongoDatabaseFactory {

/**
* Creates a default {@link MongoDatabase} instance.
*
* @return
* @throws DataAccessException
*/
MongoDatabase getMongoDatabase() throws DataAccessException;

/**
* Creates a {@link MongoDatabase} instance to access the database with the given name.
*
* @param dbName must not be {@literal null} or empty.
* @return
* @throws DataAccessException
*/
MongoDatabase getMongoDatabase(String dbName) throws DataAccessException;

/**
* Exposes a shared {@link MongoExceptionTranslator}.
*
* @return will never be {@literal null}.
*/
PersistenceExceptionTranslator getExceptionTranslator();
}

该类实现接口,并使用标准实例和数据库名称创建。​​org.springframework.data.mongodb.core.SimpleReactiveMongoDatabaseFactory​​​​ReactiveMongoDatabaseFactory​​​​com.mongodb.reactivestreams.client.MongoClient​

您可以在标准 Java 代码中使用它们,而不是使用 IoC 容器来创建实例,如下所示:​​ReactiveMongoTemplate​

public class MongoApp {

private static final Log log = LogFactory.getLog(MongoApp.class);

public static void main(String[] args) throws Exception {

ReactiveMongoOperations mongoOps = new ReactiveMongoOperations(new SimpleReactiveMongoDatabaseFactory(MongoClient.create(), "database"));

mongoOps.insert(new Person("Joe", 34))
.flatMap(p -> mongoOps.findOne(new Query(where("name").is("Joe")), Person.class))
.doOnNext(person -> log.info(person.toString()))
.flatMap(person -> mongoOps.dropCollection("person"))
.subscribe();
}
}

使用是入门部分中显示的列表之间的唯一区别。​​SimpleReactiveMongoDatabaseFactory​

13.2.3. 使用基于 Java 的元数据注册 ReactiveMongoDatabaseFactory 实例

若要向容器注册实例,可以编写与前面代码清单中突出显示的代码非常相似的代码,如以下示例所示:​​ReactiveMongoDatabaseFactory​

@Configuration
public class MongoConfiguration {

public @Bean ReactiveMongoDatabaseFactory reactiveMongoDatabaseFactory() {
return new SimpleReactiveMongoDatabaseFactory(MongoClients.create(), "database");
}
}

若要定义用户名和密码,请创建一个 MongoDB 连接字符串并将其传递到工厂方法中,如下一个清单所示。下面的清单还显示了如何使用容器注册实例:​​ReactiveMongoDatabaseFactory​​​​ReactiveMongoTemplate​

@Configuration
public class MongoConfiguration {

public @Bean ReactiveMongoDatabaseFactory reactiveMongoDatabaseFactory() {
return new SimpleReactiveMongoDatabaseFactory(MongoClients.create("mongodb://joe:secret@localhost"), "database");
}

public @Bean ReactiveMongoTemplate reactiveMongoTemplate() {
return new ReactiveMongoTemplate(reactiveMongoDatabaseFactory());
}
}

13.3. 简介​​ReactiveMongoTemplate​

Theclass位于软件包中,是Spring的Reactive MongoDB支持的核心类,并提供了丰富的功能集来与数据库进行交互。该模板提供了创建、更新、删除和查询 MongoDB 文档的便捷操作,并提供域对象和 MongoDB 文档之间的映射。​​ReactiveMongoTemplate​​​​org.springframework.data.mongodb​

配置完成后,是线程安全的,可以在多个实例中重用。​​ReactiveMongoTemplate​

MongoDB文档和域类之间的映射是通过委托给接口的实现来完成的。Spring 提供了一个默认的实现,但你也可以编写自己的转换器。有关更多详细信息,请参阅MongoConverter实例部分。​​MongoConverter​​​​MongoMappingConverter​

该类实现接口。尽可能在MongoDB驱动程序对象上提供的方法镜像方法,以使习惯于驱动程序API的现有MongoDB开发人员熟悉该API。例如,您可以找到诸如,,,,,,,和的方法。设计目标是尽可能轻松地在使用基本MongoDB驱动程序和之间转换。这两个 API 之间的主要区别是可以传递域对象而不是,并且有流畅的 API,而不是填充 a,以指定这些操作的参数。​​ReactiveMongoTemplate​​​​ReactiveMongoOperations​​​​ReactiveMongoOperations​​​​Collection​​​​find​​​​findAndModify​​​​findOne​​​​insert​​​​remove​​​​save​​​​update​​​​updateMulti​​​​ReactiveMongoOperations​​​​ReactiveMongoOperations​​​​Document​​​​Query​​​​Criteria​​​​Update​​​​Document​

引用实例操作的首选方法是通过其接口。​​ReactiveMongoTemplate​​​​ReactiveMongoOperations​

默认转换器实现由 使用。虽然可以使用其他元数据来指定对象到文档的映射,但它也可以通过使用某些约定来转换不包含其他元数据的对象,以映射 ID 和集合名称。这些约定以及映射注释的使用在映射章节中进行了说明。​​ReactiveMongoTemplate​​​​MappingMongoConverter​​​​MappingMongoConverter​

另一个核心功能是将MongoDB Java驱动程序中抛出的异常异常转换为Spring的可移植数据访问异常层次结构。有关详细信息,请参阅异常转换部分。​​ReactiveMongoTemplate​

有许多方便的方法可以帮助您轻松执行常见任务。但是,如果您需要直接访问 MongoDB 驱动程序 API 以访问 MongoTemplate 未显式公开的功能,则可以使用几种回调方法之一来访问底层驱动程序 API。回调为您提供对 aor aobject 的引用。有关更多信息,请参阅执行回调。​​ReactiveMongoTemplate​​​​execute​​​​execute​​​​com.mongodb.reactivestreams.client.MongoCollection​​​​com.mongodb.reactivestreams.client.MongoDatabase​

13.3.1. 实例化反应式Mongo模板

您可以使用 Java 创建和注册 的实例,如下所示:​​ReactiveMongoTemplate​

例 132.注册对象并启用 Spring 的异常转换支持​​com.mongodb.reactivestreams.client.MongoClient​

@Configuration
public class AppConfig {

public @Bean MongoClient reactiveMongoClient() {
return MongoClients.create("mongodb://localhost");
}

public @Bean ReactiveMongoTemplate reactiveMongoTemplate() {
return new ReactiveMongoTemplate(reactiveMongoClient(), "mydatabase");
}
}

有几个重载的构造函数,包括:​​ReactiveMongoTemplate​

  • ​ReactiveMongoTemplate(MongoClient mongo, String databaseName)​​:获取要对其操作的对象和默认数据库名称。com.mongodb.reactivestreams.client.MongoClient
  • ​ReactiveMongoTemplate(ReactiveMongoDatabaseFactory mongoDatabaseFactory)​​:采用封装了对象和数据库名称的对象。ReactiveMongoDatabaseFactorycom.mongodb.reactivestreams.client.MongoClient
  • ​ReactiveMongoTemplate(ReactiveMongoDatabaseFactory mongoDatabaseFactory, MongoConverter mongoConverter)​​:添加用于映射的 ato。MongoConverter

创建 时,可能还需要设置以下属性:​​ReactiveMongoTemplate​

  • ​WriteResultCheckingPolicy​
  • ​WriteConcern​
  • ​ReadPreference​

引用实例操作的首选方法是通过其接口。​​ReactiveMongoTemplate​​​​ReactiveMongoOperations​

13.3.2.政策​​WriteResultChecking​

在开发过程中,从任何包含错误的MongoDB操作中记录或抛出anif都很方便。在开发过程中忘记执行此操作,然后最终得到一个看起来成功运行的应用程序是很常见的,而实际上,数据库没有按照您的期望进行修改。将属性设置为具有以下值的枚举,,,或者记录错误、引发和异常或不执行任何操作。默认值是使用 avalue of。​​Exception​​​​com.mongodb.WriteResult​​​​MongoTemplate​​​​WriteResultChecking​​​​LOG​​​​EXCEPTION​​​​NONE​​​​WriteResultChecking​​​​NONE​

13.3.3. ​​WriteConcern​

如果尚未通过驱动程序在更高级别(例如)指定,则可以设置用于写入操作的属性。如果未设置 ReactiveMongoTemplate 的属性,则默认为 MongoDB 驱动程序的 sorset 中设置的属性。​​MongoDatabase​​​​com.mongodb.WriteConcern​​​​ReactiveMongoTemplate​​​​WriteConcern​​​​MongoDatabase​​​​MongoCollection​

13.3.4. ​​WriteConcernResolver​

对于您希望在每个操作的基础上设置不同值的更高级情况(用于删除、更新、插入和保存操作),可以配置调用的策略接口。由于用于持久化 POJO,因此允许您创建一个策略,该策略可以将特定的 POJO 类映射到 avalue。以下清单显示了界面:​​WriteConcern​​​​WriteConcernResolver​​​​ReactiveMongoTemplate​​​​ReactiveMongoTemplate​​​​WriteConcernResolver​​​​WriteConcern​​​​WriteConcernResolver​

public interface WriteConcernResolver {
WriteConcern resolve(MongoAction action);
}

参数 ,,确定要使用的值以及是否使用模板本身的值作为默认值。包含要写入的集合名称、POJO、转换的集合名称、作为枚举值的操作(其中之一,,,,和)以及其他一些上下文信息。以下示例演示如何创建:​​MongoAction​​​​WriteConcern​​​​MongoAction​​​​java.lang.Class​​​​DBObject​​​​MongoActionOperation​​​​REMOVE​​​​UPDATE​​​​INSERT​​​​INSERT_LIST​​​​SAVE​​​​WriteConcernResolver​

private class MyAppWriteConcernResolver implements WriteConcernResolver {

public WriteConcern resolve(MongoAction action) {
if (action.getEntityClass().getSimpleName().contains("Audit")) {
return WriteConcern.NONE;
} else if (action.getEntityClass().getSimpleName().contains("Metadata")) {
return WriteConcern.JOURNAL_SAFE;
}
return action.getDefaultWriteConcern();
}
}

13.4. 保存、更新和删除文档

​ReactiveMongoTemplate​​允许您保存、更新和删除域对象,并将这些对象映射到存储在 MongoDB 中的文档。

请考虑以下类:​​Person​

public class Person {

private String id;
private String name;
private int age;

public Person(String name, int age) {
this.name = name;
this.age = age;
}

public String getId() {
return id;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}

@Override
public String toString() {
return "Person [id=" + id + ", name=" + name + ", age=" + age + "]";
}

}

The following listing shows how you can save, update, and delete the object:​​Person​

public class ReactiveMongoApp {

private static final Logger log = LoggerFactory.getLogger(ReactiveMongoApp.class);

public static void main(String[] args) throws Exception {

CountDownLatch latch = new CountDownLatch(1);

ReactiveMongoTemplate mongoOps = new ReactiveMongoTemplate(MongoClients.create(), "database");

mongoOps.insert(new Person("Joe", 34)).doOnNext(person -> log.info("Insert: " + person))
.flatMap(person -> mongoOps.findById(person.getId(), Person.class))
.doOnNext(person -> log.info("Found: " + person))
.zipWith(person -> mongoOps.updateFirst(query(where("name").is("Joe")), update("age", 35), Person.class))
.flatMap(tuple -> mongoOps.remove(tuple.getT1())).flatMap(deleteResult -> mongoOps.findAll(Person.class))
.count().doOnSuccess(count -> {
log.info("Number of people: " + count);
latch.countDown();
})

.subscribe();

latch.await();
}
}

The preceding example includes implicit conversion between a and (by using the ) as stored in the database and recognizing a convention of the property name.​​String​​​​ObjectId​​​​MongoConverter​​​​Id​

The preceding example is meant to show the use of save, update, and remove operations on and not to show complex mapping or chaining functionality. ​​ReactiveMongoTemplate​

“Querying Documents” explains the query syntax used in the preceding example in more detail. Additional documentation can be found in the blocking MongoTemplate section.

13.5. Execution Callbacks

所有 Spring 模板类的一个共同设计特征是,所有功能都路由到运行回调方法的模板之一中。这有助于确保异常和可能需要的任何资源管理执行的一致性。虽然在JDBC和JMS的情况下,这比MongoDB更需要,但它仍然为异常转换和日志记录提供了一个单一的位置。因此,使用回调是访问MongoDB驱动程序的sand对象以执行未作为方法公开的不常见操作的首选方法。​​execute​​​​MongoDatabase​​​​MongoCollection​​​​ReactiveMongoTemplate​

下面是回调方法的列表。​​execute​

  • ​<T> Flux<T>​​ execute:为指定类的实体集合运行给定的。(Class<?> entityClass, ReactiveCollectionCallback<T> action)ReactiveCollectionCallback
  • ​<T> Flux<T>​​ execute:运行给定名称的集合。(String collectionName, ReactiveCollectionCallback<T> action)ReactiveCollectionCallback
  • ​<T> Flux<T>​​ 执行:根据需要运行翻译任何异常。(ReactiveDatabaseCallback<T> action)ReactiveDatabaseCallback

以下示例使用 theto 返回有关索引的信息:​​ReactiveCollectionCallback​

Flux<Boolean> hasIndex = operations.execute("geolocation",
collection -> Flux.from(collection.listIndexes(Document.class))
.filter(document -> document.get("name").equals("fancy-index-name"))
.flatMap(document -> Mono.just(true))
.defaultIfEmpty(false));

13.6. 网格FS支持

MongoDB支持将二进制文件存储在其文件系统GridFS中。 Spring Data MongoDB提供了一个接口以及相应的实现,让你与文件系统进行交互。 您可以通过将 aas 和 a 交给它来设置实例,如以下示例所示:​​ReactiveGridFsOperations​​​​ReactiveGridFsTemplate​​​​ReactiveGridFsTemplate​​​​ReactiveMongoDatabaseFactory​​​​MongoConverter​

例 133.ReactiveGridFsTemplate 的 JavaConfig setup

class GridFsConfiguration extends AbstractReactiveMongoConfiguration {

// … further configuration omitted

@Bean
public ReactiveGridFsTemplate reactiveGridFsTemplate() {
return new ReactiveGridFsTemplate(reactiveMongoDbFactory(), mappingMongoConverter());
}
}

现在可以注入模板并用于执行存储和检索操作,如以下示例所示:

例 134.使用 ReactiveGridFsTemplate 存储文件

class ReactiveGridFsClient {

@Autowired
ReactiveGridFsTemplate operations;

@Test
public Mono<ObjectId> storeFileToGridFs() {

FileMetadata metadata = new FileMetadata();
// populate metadata
Publisher<DataBuffer> file = … // lookup File or Resource

return operations.store(file, "filename.txt", metadata);
}
}

这些操作采用有关要存储的文件的文件名和(可选)元数据信息。元数据可以是任意对象,该对象将由配置的 封送。或者,您也可以提供 a.​​store(…)​​​​Publisher<DataBuffer>​​​​MongoConverter​​​​ReactiveGridFsTemplate​​​​Document​

MongoDB的驱动程序使用和接口来交换二进制流。Spring Data MongoDB适应了这些接口。阅读更多关于Spring 的参考文档​。​​AsyncInputStream​​​​AsyncOutputStream​​​​Publisher<DataBuffer>​​​​DataBuffer​

您可以通过这些方法从文件系统中读取文件。我们先来看看方法。您可以找到单个文件或与 a 匹配的多个文件。可以使用帮助程序类来定义查询。它提供静态工厂方法来封装默认元数据字段(如 and)或自定义元数据字段。下面的示例演示如何使用 to 查询文件:​​find(…)​​​​getResources(…)​​​​find(…)​​​​Query​​​​GridFsCriteria​​​​whereFilename()​​​​whereContentType()​​​​whereMetaData()​​​​ReactiveGridFsTemplate​

例 135。使用 ReactiveGridFsTemplate 查询文件

class ReactiveGridFsClient {

@Autowired
ReactiveGridFsTemplate operations;

@Test
public Flux<GridFSFile> findFilesInGridFs() {
return operations.find(query(whereFilename().is("filename.txt")))
}
}

目前,MongoDB不支持在从GridFS检索文件时定义排序条件。因此,将忽略在传递给该方法的实例上定义的任何排序标准。​​Query​​​​find(…)​

从 GridFs 读取文件的另一个选项是使用沿以下行建模的方法。使用 reactive 类型来延迟运行,同时使用同步接口。 这些方法允许将 Ant 路径传递到方法中,从而可以检索与给定模式匹配的文件。以下示例演示如何使用读取文件:​​ResourcePatternResolver​​​​ReactiveGridFsOperations​​​​ResourcePatternResolver​​​​ReactiveGridFsTemplate​

例 136.使用 ReactiveGridFsTemplate 读取文件

class ReactiveGridFsClient {

@Autowired
ReactiveGridFsOperations operations;

@Test
public void readFilesFromGridFs() {
Flux<ReactiveGridFsResource> txtFiles = operations.getResources("*.txt");
}
}

14. MongoDB 存储库

本章指出了 MongoDB 存储库支持的特殊性。 本章建立在使用 Spring 数据存储库中解释的核心存储库支持之上。 您应该对那里解释的基本概念有很好的理解。

14.1. 用法

要访问存储在MongoDB中的域实体,您可以使用我们复杂的存储库支持,这大大简化了实施。 为此,请为存储库创建一个接口,如以下示例所示:

例 137.示例人员实体

public class Person {

@Id
private String id;
private String firstname;
private String lastname;
private Address address;

// … getters and setters omitted
}

请注意,前面示例中所示的域类型具有名为 ndof 类型的属性。中使用的默认序列化机制(支持存储库支持)将属性命名为文档 ID。 目前,我们支持和 ID 类型。 有关如何在映射图层中处理字段的详细信息,请参阅ID 映射。​​id​​​​String​​​​MongoTemplate​​​​id​​​​String​​​​ObjectId​​​​BigInteger​​​​id​

现在我们有一个域对象,我们可以定义一个使用它的接口,如下所示:

例 138.用于保留人员实体的基本存储库接口

public interface PersonRepository extends PagingAndSortingRepository<Person, String> {

// additional custom query methods go here
}

现在这个接口仅用于提供类型信息,但我们稍后可以向其添加其他方法。

要开始使用存储库,请使用注释。 该批注具有与命名空间元素相同的属性。 如果未配置基本包,基础结构将扫描带批注的配置类的包。 以下示例演示如何将应用程序配置为使用 MongoDB 存储库:​​@EnableMongoRepositories​

爪哇岛

.XML

@Configuration
@EnableMongoRepositories("com.acme.*.repositories")
class ApplicationConfig extends AbstractMongoClientConfiguration {

@Override
protected String getDatabaseName() {
return "e-store";
}

@Override
protected String getMappingBasePackage() {
return "com.acme.*.repositories";
}
}

此命名空间元素导致扫描基本包以查找扩展接口,并为找到的每个接口创建 Spring bean。 默认情况下,存储库会获得调用的 Spring Bean 连线,因此只有在偏离此约定时,才需要显式配置。​​MongoRepository​​​​MongoTemplate​​​​mongoTemplate​​​​mongo-template-ref​

由于我们的域存储库可扩展,因此它为您提供了 CRUD 操作以及对实体进行分页和排序访问的方法。 使用存储库实例只是将其注入客户端的依赖项问题。 因此,访问页面大小为 10 的对象第二页将类似于以下代码:​​PagingAndSortingRepository​​​​Person​

例 139.对个人实体的分页访问

@ExtendWith(SpringExtension.class)
@ContextConfiguration
class PersonRepositoryTests {

@Autowired PersonRepository repository;

@Test
void readsFirstPageCorrectly() {

Page<Person> persons = repository.findAll(PageRequest.of(0, 10));
assertThat(persons.isFirstPage()).isTrue();
}
}

前面的示例使用Spring的单元测试支持创建了一个应用程序上下文,该上下文将基于注释的依赖项注入到测试用例中。 在测试方法中,我们使用存储库来查询数据存储。 我们向存储库提供一个实例,该实例以 10 的页面大小请求对象的第一页。​​PageRequest​​​​Person​

14.2. 查询方法

您通常在存储库上触发的大多数数据访问操作都会导致对MongoDB数据库执行查询。 定义此类查询是在存储库接口上声明方法的问题,如以下示例所示:

例 140.具有查询方法的人员存储库

public interface PersonRepository extends PagingAndSortingRepository<Person, String> {

List<Person> findByLastname(String lastname);

Page<Person> findByFirstname(String firstname, Pageable pageable);

Person findByShippingAddresses(Address address);

Person findFirstByLastname(String lastname)

Stream<Person> findAllBy();
}

该方法显示具有给定姓氏的所有用户的查询。 查询是通过分析可与 and 连接的约束的方法名称派生的。 因此,方法名称导致查询表达式。​​findByLastname​​​​And​​​​Or​​​​{"lastname" : lastname}​

将分页应用于查询。 您可以为方法签名配备参数,并让方法返回实例,Spring 数据会自动相应地对查询进行分页。​​Pageable​​​​Page​

显示您可以基于非基元类型的属性进行查询。 如果找到多个匹配项,则投掷。​​IncorrectResultSizeDataAccessException​

使用关键字将查询限制为仅第一个结果。 与 <3> 不同,如果找到多个匹配项,此方法不会引发异常。​​First​

使用 Java 8,在迭代流时读取和转换单个元素。​​Stream​

我们不支持引用映射为域类中的参数。​​DBRef​

下表显示了查询方法支持的关键字:

表 9.查询方法支持的关键字

关键词

样本

逻辑结果

​After​

​findByBirthdateAfter(Date date)​

​{"birthdate" : {"$gt" : date}}​

​GreaterThan​

​findByAgeGreaterThan(int age)​

​{"age" : {"$gt" : age}}​

​GreaterThanEqual​

​findByAgeGreaterThanEqual(int age)​

​{"age" : {"$gte" : age}}​

​Before​

​findByBirthdateBefore(Date date)​

​{"birthdate" : {"$lt" : date}}​

​LessThan​

​findByAgeLessThan(int age)​

​{"age" : {"$lt" : age}}​

​LessThanEqual​

​findByAgeLessThanEqual(int age)​

​{"age" : {"$lte" : age}}​

​Between​

​findByAgeBetween(int from, int to)​​​​findByAgeBetween(Range<Integer> range)​

​{"age" : {"$gt" : from, "$lt" : to}}​​下限/上限 (/&/) 根据​​$gt​​​​$gte​​​​$lt​​​​$lte​​​​Range​

​In​

​findByAgeIn(Collection ages)​

​{"age" : {"$in" : [ages…]}}​

​NotIn​

​findByAgeNotIn(Collection ages)​

​{"age" : {"$nin" : [ages…]}}​

​IsNotNull​​​, ​​NotNull​

​findByFirstnameNotNull()​

​{"firstname" : {"$ne" : null}}​

​IsNull​​​, ​​Null​

​findByFirstnameNull()​

​{"firstname" : null}​

​Like​​​, , ​​StartingWith​​​​EndingWith​

​findByFirstnameLike(String name)​

​{"firstname" : name} (name as regex)​

​NotLike​​​, ​​IsNotLike​

​findByFirstnameNotLike(String name)​

​{"firstname" : { "$not" : name }} (name as regex)​

​Containing​​在字符串上

​findByFirstnameContaining(String name)​

​{"firstname" : name} (name as regex)​

​NotContaining​​在字符串上

​findByFirstnameNotContaining(String name)​

​{"firstname" : { "$not" : name}} (name as regex)​

​Containing​​在收集

​findByAddressesContaining(Address address)​

​{"addresses" : { "$in" : address}}​

​NotContaining​​在收集

​findByAddressesNotContaining(Address address)​

​{"addresses" : { "$not" : { "$in" : address}}}​

​Regex​

​findByFirstnameRegex(String firstname)​

​{"firstname" : {"$regex" : firstname }}​

​(No keyword)​

​findByFirstname(String name)​

​{"firstname" : name}​

​Not​

​findByFirstnameNot(String name)​

​{"firstname" : {"$ne" : name}}​

​Near​

​findByLocationNear(Point point)​

​{"location" : {"$near" : [x,y]}}​

​Near​

​findByLocationNear(Point point, Distance max)​

​{"location" : {"$near" : [x,y], "$maxDistance" : max}}​

​Near​

​findByLocationNear(Point point, Distance min, Distance max)​

​{"location" : {"$near" : [x,y], "$minDistance" : min, "$maxDistance" : max}}​

​Within​

​findByLocationWithin(Circle circle)​

​{"location" : {"$geoWithin" : {"$center" : [ [x, y], distance]}}}​

​Within​

​findByLocationWithin(Box box)​

​{"location" : {"$geoWithin" : {"$box" : [ [x1, y1], x2, y2]}}}​

​IsTrue​​​, ​​True​

​findByActiveIsTrue()​

​{"active" : true}​

​IsFalse​​​, ​​False​

​findByActiveIsFalse()​

​{"active" : false}​

​Exists​

​findByLocationExists(boolean exists)​

​{"location" : {"$exists" : exists }}​

​IgnoreCase​

​findByUsernameIgnoreCase(String username)​

​{"username" : {"$regex" : "^username$", "$options" : "i" }}​


如果属性标准比较文档,则字段中字段的顺序和文档中的完全相等性很重要。

14.2.1. 仓库更新方法

还可以使用上表中的关键字创建查询,以标识匹配的文档以对其运行更新。 实际的更新操作由方法本身的注释定义,如下面的清单所示。 请注意,派生查询的命名架构以 开头。 使用(如在)只允许与 .​​@Update​​​​find​​​​update​​​​updateAllByLastname(…)​​​​@Query​

此更新将应用于所有匹配的文档,并且无法通过使用任何限制关键字传入 aor 来限制范围。 返回类型可以是任一类型,也可以是数字类型,例如,用于保存已修改文档的数量。​​Page​​​​void​​​​long​

例 141.更新方法

public interface PersonRepository extends CrudRepository<Person, String> {

@Update("{ '$inc' : { 'visits' : 1 } }")
long findAndIncrementVisitsByLastname(String lastname);

@Update("{ '$inc' : { 'visits' : ?1 } }")
void findAndIncrementVisitsByLastname(String lastname, int increment);

@Update("{ '$inc' : { 'visits' : ?#{[1]} } }")
long findAndIncrementVisitsUsingSpELByLastname(String lastname, int increment);

@Update(pipeline = {"{ '$set' : { 'visits' : { '$add' : [ '$visits', ?1 ] } } }"})
void findAndIncrementVisitsViaPipelineByLastname(String lastname, int increment);

@Update("{ '$push' : { 'shippingAddresses' : ?1 } }")
long findAndPushShippingAddressByEmail(String email, Address address);

@Query("{ 'lastname' : ?0 }")
@Update("{ '$inc' : { 'visits' : ?1 } }")
void updateAllByLastname(String lastname, int increment);
}

更新的筛选器查询派生自方法名称。 更新按“原样”进行,不绑定任何参数。

实际增量值由绑定到占位符的方法参数定义。​​increment​​​​?1​

使用 Spring 表达式语言 (SpEL) 进行参数绑定。

使用该属性发布聚合管道更新​。​​pipeline​

更新可能包含复杂对象。

将基于字符串的查询与更新相结合。

存储库更新不会发出持久性,也不会映射生命周期事件。

14.2.2. 仓库删除查询

上表中的关键字可以与创建删除匹配文档的查询结合使用。​​delete…By​​​​remove…By​

例 142.查询​​Delete…By​

public interface PersonRepository extends MongoRepository<Person, String> {

List <Person> deleteByLastname(String lastname);

Long deletePersonByLastname(String lastname);

@Nullable
Person deleteSingleByLastname(String lastname);

Optional<Person> deleteByBirthdate(Date birthdate);
}

使用 return 类型 of检索并返回所有匹配的文档,然后再实际删除它们。​​List​

数字返回类型直接删除匹配的文档,返回删除的文档总数。

单个域类型结果检索并删除第一个匹配的文档。

与 3 相同,但包裹在 antype 中。​​Optional​

14.2.3. 地理空间存储库查询

正如您在前面的关键字表中所看到的,一些关键字会在 MongoDB 查询中触发地理空间操作。 关键字允许进一步修改,如下几个示例所示。​​Near​

下面的示例演示如何定义查找给定点给定距离的所有人员的查询:​​near​

例 143.高级查询​​Near​

public interface PersonRepository extends MongoRepository<Person, String> {

// { 'location' : { '$near' : [point.x, point.y], '$maxDistance' : distance}}
List<Person> findByLocationNear(Point location, Distance distance);
}

向查询方法添加参数允许将结果限制为给定距离内的结果。 如果设置包含 a,我们透明地使用 代替,如以下示例所示:​​Distance​​​​Distance​​​​Metric​​​​$nearSphere​​​​$code​

例 144.使用与​​Distance​​​​Metrics​

Point point = new Point(43.7, 48.8);
Distance distance = new Distance(200, Metrics.KILOMETERS);
… = repository.findByLocationNear(point, distance);
// {'location' : {'$nearSphere' : [43.7, 48.8], '$maxDistance' : 0.03135711885774796}}

使用 awith 会导致添加(而不是普通)子句。 除此之外,实际距离将根据使用量计算。​​Distance​​​​Metric​​​​$nearSphere​​​​$near​​​​Metrics​

(请注意,这不是指公制度量单位。 它可能是英里而不是公里。 相反,指的是测量系统的概念,无论您使用哪种系统。​​Metric​​​​metric​

在目标属性上使用强制使用运算符。​​@GeoSpatialIndexed(type = GeoSpatialIndexType.GEO_2DSPHERE)​​​​$nearSphere​

地理邻近查询

Spring Data MongoDb 支持地理邻近查询,如以下示例所示:

public interface PersonRepository extends MongoRepository<Person, String> {

// {'geoNear' : 'location', 'near' : [x, y] }
GeoResults<Person> findByLocationNear(Point location);

// No metric: {'geoNear' : 'person', 'near' : [x, y], maxDistance : distance }
// Metric: {'geoNear' : 'person', 'near' : [x, y], 'maxDistance' : distance,
// 'distanceMultiplier' : metric.multiplier, 'spherical' : true }
GeoResults<Person> findByLocationNear(Point location, Distance distance);

// Metric: {'geoNear' : 'person', 'near' : [x, y], 'minDistance' : min,
// 'maxDistance' : max, 'distanceMultiplier' : metric.multiplier,
// 'spherical' : true }
GeoResults<Person> findByLocationNear(Point location, Distance min, Distance max);

// {'geoNear' : 'location', 'near' : [x, y] }
GeoResults<Person> findByLocationNear(Point location);
}

14.2.4. 基于 MongoDB JSON 的查询方法和字段限制

通过将注释添加到存储库查询方法,您可以指定要使用的 MongoDB JSON 查询字符串,而不是从方法名称派生查询,如以下示例所示:​​org.springframework.data.mongodb.repository.Query​

public interface PersonRepository extends MongoRepository<Person, String> {

@Query("{ 'firstname' : ?0 }")
List<Person> findByThePersonsFirstname(String firstname);

}

占位符允许您将方法参数中的值替换为 JSON 查询字符串。​​?0​

​String​​参数值在绑定过程中被转义,这意味着无法通过参数添加 MongoDB 特定的运算符。

还可以使用 filter 属性来限制映射到 Java 对象的属性集,如以下示例所示:

public interface PersonRepository extends MongoRepository<Person, String> {

@Query(value="{ 'firstname' : ?0 }", fields="{ 'firstname' : 1, 'lastname' : 1}")
List<Person> findByThePersonsFirstname(String firstname);

}

前面示例中的查询仅返回对象的 AND 属性。 属性 a 未设置,因此其值为 null。​​firstname​​​​lastname​​​​Id​​​​Person​​​​age​​​​java.lang.Integer​

14.2.5. 排序查询方法结果

MongoDB存储库允许各种方法来定义排序顺序。 让我们看一下以下示例:

例 145.对查询结果进行排序

public interface PersonRepository extends MongoRepository<Person, String> {

List<Person> findByFirstnameSortByAgeDesc(String firstname);

List<Person> findByFirstname(String firstname, Sort sort);

@Query(sort = "{ age : -1 }")
List<Person> findByFirstname(String firstname);

@Query(sort = "{ age : -1 }")
List<Person> findByLastname(String lastname, Sort sort);
}

从方法名称派生的静态排序。结果用于排序参数。​​SortByAgeDesc​​​​{ age : -1 }​

使用方法 argument.create for sort 参数进行动态排序。​​Sort.by(DESC, "age")​​​​{ age : -1 }​

静态排序通过注释。 按属性中所述应用的排序参数。​​Query​​​​sort​

默认排序通过注释与动态注释相结合,通过方法参数。 使用覆盖默认值并创建。更改默认值并导致结果。​​Query​​​​Sort.unsorted()​​​​{ age : -1 }​​​​Sort.by(ASC, "age")​​​​{ age : 1 }​​​​Sort.by (ASC, "firstname")​​​​{ age : -1, firstname : 1 }​

14.2.6. 使用 SpEL 表达式的基于 JSON 的查询

查询字符串和字段定义可以与 SpEL 表达式一起使用,以在运行时创建动态查询。 SpEL 表达式可以提供谓词值,并可用于使用子文档扩展谓词。

表达式通过包含所有参数的数组公开方法参数。 以下查询用于声明谓词值(等效于参数绑定):​​[0]​​​​lastname​​​​?0​

public interface PersonRepository extends MongoRepository<Person, String> {

@Query("{'lastname': ?#{[0]} }")
List<Person> findByQueryWithExpression(String param0);
}

表达式可用于调用函数、计算条件和构造值。 与 JSON 结合使用的 SpEL 表达式揭示了一个副作用,因为 SpEL 中类似 Map 的声明读起来像 JSON,如以下示例所示:

public interface PersonRepository extends MongoRepository<Person, String> {

@Query("{'id': ?#{ [0] ? {$exists :true} : [1] }}")
List<Person> findByQueryWithExpressionAndNestedObject(boolean param0, String param1);
}


查询字符串中的 SpEL 是增强查询的强大方法。 但是,他们也可以接受各种不需要的论点。 请确保在将字符串传递给查询之前对其进行清理,以避免创建漏洞或对查询进行不必要的更改。

表达式支持可通过查询 SPI: 进行扩展。 查询 SPI 可以提供属性和函数,并且可以自定义根对象。 在生成查询时,在进行 SpEL 评估时,将从应用程序上下文中检索扩展。 以下示例演示如何使用:​​org.springframework.data.repository.query.spi.EvaluationContextExtension​​​​EvaluationContextExtension​

public class SampleEvaluationContextExtension extends EvaluationContextExtensionSupport {

@Override
public String getExtensionId() {
return "security";
}

@Override
public Map<String, Object> getProperties() {
return Collections.singletonMap("principal", SecurityContextHolder.getCurrent().getPrincipal());
}
}

引导自身不是应用程序上下文感知的,需要进一步配置才能获取查询 SPI 扩展。​​MongoRepositoryFactory​

反应式查询方法可以利用。​​org.springframework.data.spel.spi.ReactiveEvaluationContextExtension​

14.2.7. 类型安全的查询方法

MongoDB存储库支持与Querydsl项目集成,该项目提供了一种执行类型安全查询的方法。 引用项目描述,“不是将查询编写为内联字符串或将它们外部化为 XML 文件,而是通过流畅的 API 构建。它提供以下功能:

  • 在 IDE 中完成代码(所有属性、方法和操作都可以在您喜欢的 Java IDE 中展开)。
  • 几乎不允许语法无效的查询(所有级别的类型安全)。
  • 可以安全地引用域类型和属性 - 不涉及字符串!
  • 更好地适应域类型的重构更改。
  • 增量查询定义更容易。

请参阅QueryDSL 文档,了解如何使用 Maven 或 Ant 引导环境进行基于 APT 的代码生成。

QueryDSL 允许您编写如下查询:

QPerson person = new QPerson("person");
List<Person> result = repository.findAll(person.address.zipCode.eq("C0123"));

Page<Person> page = repository.findAll(person.lastname.contains("a"),
PageRequest.of(0, 2, Direction.ASC, "lastname"));

​QPerson​​是由 Java 注解后处理工具生成的类。 它允许您编写类型安全的查询。 请注意,查询中除了值之外没有其他字符串。​​Predicate​​​​C0123​

您可以通过接口来使用生成的类,以下清单显示了该接口:​​Predicate​​​​QuerydslPredicateExecutor​

public interface QuerydslPredicateExecutor<T> {

T findOne(Predicate predicate);

List<T> findAll(Predicate predicate);

List<T> findAll(Predicate predicate, OrderSpecifier<?>... orders);

Page<T> findAll(Predicate predicate, Pageable pageable);

Long count(Predicate predicate);
}

要在存储库实现中使用它,请将其添加到接口继承的存储库接口列表中,如以下示例所示:

public interface PersonRepository extends MongoRepository<Person, String>, QuerydslPredicateExecutor<Person> {

// additional query methods go here
}

14.2.8. 全文搜索查询

MongoDB的全文搜索功能是特定于存储的,因此可以在更通用的搜索功能上找到。 我们需要一个具有全文索引的文档(请参阅“文本索引”以了解如何创建全文索引)。​​MongoRepository​​​​CrudRepository​

其他方法作为输入参数。 除了这些显式方法之外,还可以添加派生存储库方法。 这些条件将作为附加条件添加。 一旦实体包含带批注的属性,就可以检索文档的全文分数。 此外,注释还可以按文档的分数进行排序,如以下示例所示:​​MongoRepository​​​​TextCriteria​​​​TextCriteria​​​​AND​​​​@TextScore​​​​@TextScore​

@Document
class FullTextDocument {

@Id String id;
@TextIndexed String title;
@TextIndexed String content;
@TextScore Float score;
}

interface FullTextRepository extends Repository<FullTextDocument, String> {

// Execute a full-text search and define sorting dynamically
List<FullTextDocument> findAllBy(TextCriteria criteria, Sort sort);

// Paginate over a full-text search result
Page<FullTextDocument> findAllBy(TextCriteria criteria, Pageable pageable);

// Combine a derived query with a full-text search
List<FullTextDocument> findByTitleOrderByScoreDesc(String title, TextCriteria criteria);
}


Sort sort = Sort.by("score");
TextCriteria criteria = TextCriteria.forDefaultLanguage().matchingAny("spring", "data");
List<FullTextDocument> result = repository.findAllBy(criteria, sort);

criteria = TextCriteria.forDefaultLanguage().matching("film");
Page<FullTextDocument> page = repository.findAllBy(criteria, PageRequest.of(1, 1, sort));
List<FullTextDocument> result = repository.findByTitleOrderByScoreDesc("mongodb", criteria);

14.2.9. 预测

Spring 数据查询方法通常返回由存储库管理的聚合根的一个或多个实例。 但是,有时可能需要基于这些类型的某些属性创建投影。 Spring 数据允许对专用返回类型进行建模,以更有选择性地检索托管聚合的部分视图。

假设存储库和聚合根类型,如以下示例所示:

例 146.示例聚合和存储库

class Person {

@Id UUID id;
String firstname, lastname;
Address address;

static class Address {
String zipCode, city, street;
}
}

interface PersonRepository extends Repository<Person, UUID> {

Collection<Person> findByLastname(String lastname);
}

现在假设我们只想检索人员的姓名属性。 Spring Data提供了什么手段来实现这一目标?本章的其余部分将回答这个问题。

基于接口的投影

将查询结果限制为仅 name 属性的最简单方法是声明一个接口,该接口公开要读取的属性的访问器方法,如以下示例所示:

例 147.用于检索属性子集的投影接口

interface NamesOnly {

String getFirstname();
String getLastname();
}

这里重要的一点是,此处定义的属性与聚合根中的属性完全匹配。 这样做可以添加查询方法,如下所示:

例 148.使用基于接口的投影和查询方法的存储库

interface PersonRepository extends Repository<Person, UUID> {

Collection<NamesOnly> findByLastname(String lastname);
}

查询执行引擎在运行时为返回的每个元素创建该接口的代理实例,并将对公开方法的调用转发到目标对象。

在 yourThat 中声明方法会覆盖基方法(例如,声明在、特定于存储的存储库接口或 the)会导致对基方法的调用,而不管声明的返回类型如何。请确保使用兼容的返回类型,因为基方法不能用于投影。某些存储模块支持注释,以将重写的基方法转换为查询方法,然后可用于返回投影。​​Repository​​​​CrudRepository​​​​Simple…Repository​​​​@Query​

投影可以递归使用。如果还希望包含某些信息,请为其创建一个投影接口,并从声明中返回该接口,如以下示例所示:​​Address​​​​getAddress()​

例 149.用于检索属性子集的投影接口

interface PersonSummary {

String getFirstname();
String getLastname();
AddressSummary getAddress();

interface AddressSummary {
String getCity();
}
}

在方法调用时,将获取目标实例的属性并依次包装到投影代理中。​​address​

封闭式投影

其访问器方法都与目标聚合的属性匹配的投影接口被视为封闭投影。以下示例(我们在本章前面也使用过)是一个封闭投影:

例 150.封闭投影

interface NamesOnly {

String getFirstname();
String getLastname();
}

如果您使用封闭投影,Spring Data 可以优化查询执行,因为我们知道支持投影代理所需的所有属性。 有关这方面的更多详细信息,请参阅参考文档中特定于模块的部分。

开放投影

投影接口中的访问器方法还可用于通过注释来计算新值,如以下示例所示:​​@Value​

例 151.开放式投影

interface NamesOnly {

@Value("#{target.firstname + ' ' + target.lastname}")
String getFullName();

}

支持投影的聚合根在变量中可用。 投影接口使用是开放式投影。 在这种情况下,Spring 数据无法应用查询执行优化,因为 SpEL 表达式可以使用聚合根的任何属性。​​target​​​​@Value​

使用的表达式不应该太复杂 — 您希望避免对变量进行编程。 对于非常简单的表达式,一种选择可能是采用默认方法(Java 8 中引入),如以下示例所示:​​@Value​​​​String​

例 152.使用自定义逻辑的默认方法的投影接口

interface NamesOnly {

String getFirstname();
String getLastname();

default String getFullName() {
return getFirstname().concat(" ").concat(getLastname());
}
}

此方法要求您能够完全基于投影接口上公开的其他访问器方法实现逻辑。 第二个更灵活的选项是在 Spring Bean 中实现自定义逻辑,然后从 SpEL 表达式中调用该逻辑,如以下示例所示:

例 153。示例人员对象

@Component
class MyBean {

String getFullName(Person person) {

}
}

interface NamesOnly {

@Value("#{@myBean.getFullName(target)}")
String getFullName();

}

请注意 SpEL 表达式如何引用和调用该方法,并将投影目标作为方法参数转发。 SpEL 表达式评估支持的方法也可以使用方法参数,然后可以从表达式中引用这些参数。 方法参数可通过名为的数组获得。下面的示例演示如何从数组中获取方法参数:​​myBean​​​​getFullName(…)​​​​Object​​​​args​​​​args​

例 154。示例人员对象

interface NamesOnly {

@Value("#{args[0] + ' ' + target.firstname + '!'}")
String getSalutation(String prefix);
}

同样,对于更复杂的表达式,您应该使用 Spring Bean 并让表达式调用方法,如前所述。

可为空的包装器

投影接口中的 Getter 可以使用可为空的包装器来提高空安全性。目前支持的包装器类型包括:

  • ​java.util.Optional​
  • ​com.google.common.base.Optional​
  • ​scala.Option​
  • ​io.vavr.control.Option​

例 155.使用可为空包装器的投影接口

interface NamesOnly {

Optional<String> getFirstname();
}

如果基础投影值不是,则使用包装器的当前表示形式返回值。 如果支持值为 ,则 getter 方法返回所用包装类型的空表示形式。​​null​​​​null​

基于类的投影 (DTO)

定义投影的另一种方法是使用值类型 DTO(数据传输对象),用于保存应检索的字段的属性。 这些 DTO 类型的使用方式与投影接口的使用方式完全相同,只是不会发生代理,并且不能应用嵌套投影。

如果存储通过限制要加载的字段来优化查询执行,则要加载的字段由公开的构造函数的参数名称确定。

以下示例显示了一个投影 DTO:

例 156.一个突出的DTO

class NamesOnly {

private final String firstname, lastname;

NamesOnly(String firstname, String lastname) {

this.firstname = firstname;
this.lastname = lastname;
}

String getFirstname() {
return this.firstname;
}

String getLastname() {
return this.lastname;
}

// equals(…) and hashCode() implementations
}

避免投影 DTO 的样板代码


你可以通过使用ProjectLombok​来大大简化DTO的代码,它提供了anannotation(不要与前面的接口示例中所示的Spring'sannotation混淆)。 如果您使用龙目岛项目的注释,前面显示的示例 DTO 将变为以下内容:​​@Value​​​​@Value​​​​@Value​




@Value
class NamesOnly {
String firstname, lastname;
}




字段是默认的,该类公开一个构造函数,该构造函数采用所有字段并自动获取实现的 sand方法。​​private final​​​​equals(…)​​​​hashCode()​


动态投影

到目前为止,我们已经使用投影类型作为集合的返回类型或元素类型。 但是,您可能希望选择要在调用时使用的类型(这使其成为动态的)。 若要应用动态投影,请使用查询方法,如以下示例所示:

例 157.使用动态投影参数的存储库

interface PersonRepository extends Repository<Person, UUID> {

<T> Collection<T> findByLastname(String lastname, Class<T> type);
}

这样,该方法可用于按原样获取聚合或应用投影,如以下示例所示:

例 158.使用具有动态投影的存储库

void someMethod(PersonRepository people) {

Collection<Person> aggregates =
people.findByLastname("Matthews", Person.class);

Collection<NamesOnly> aggregates =
people.findByLastname("Matthews", NamesOnly.class);
}

检查类型的查询参数是否符合动态投影参数的条件。 如果查询的实际返回类型等于参数的泛型参数类型,则匹配参数不可用于查询或 SpEL 表达式。 如果要使用 aparameter 作为查询参数,请确保使用其他泛型参数,例如。​​Class​​​​Class​​​​Class​​​​Class​​​​Class<?>​

14.2.10. 聚合存储库方法

存储库层提供了通过带注释的存储库查询方法与聚合框架进行交互的方法。 与基于 JSON 的查询类似,您可以使用注释定义管道。 该定义可能包含简单的占位符,例如SpEL 表达式。​​org.springframework.data.mongodb.repository.Aggregation​​​​?0​​​​?#{ … }​

例 159.聚合存储库方法

public interface PersonRepository extends CrudReppsitory<Person, String> {

@Aggregation("{ $group: { _id : $lastname, names : { $addToSet : $firstname } } }")
List<PersonAggregate> groupByLastnameAndFirstnames();

@Aggregation("{ $group: { _id : $lastname, names : { $addToSet : $firstname } } }")
List<PersonAggregate> groupByLastnameAndFirstnames(Sort sort);

@Aggregation("{ $group: { _id : $lastname, names : { $addToSet : ?0 } } }")
List<PersonAggregate> groupByLastnameAnd(String property);

@Aggregation("{ $group: { _id : $lastname, names : { $addToSet : ?0 } } }")
Slice<PersonAggregate> groupByLastnameAnd(String property, Pageable page);

@Aggregation("{ $group: { _id : $lastname, names : { $addToSet : $firstname } } }")
Stream<PersonAggregate> groupByLastnameAndFirstnamesAsStream();

@Aggregation("{ $group : { _id : null, total : { $sum : $age } } }")
SumValue sumAgeUsingValueWrapper();

@Aggregation("{ $group : { _id : null, total : { $sum : $age } } }")
Long sumAge();

@Aggregation("{ $group : { _id : null, total : { $sum : $age } } }")
AggregationResults<SumValue> sumAgeRaw();

@Aggregation("{ '$project': { '_id' : '$lastname' } }")
List<String> findAllLastnames();
}
public class PersonAggregate {

private @Id String lastname;
private List<String> names;

public PersonAggregate(String lastname, List<String> names) {
// ...
}

// Getter / Setter omitted
}

public class SumValue {

private final Long total;

public SumValue(Long total) {
// ...
}

// Getter omitted
}

聚合管道,用于按返回这些名称的集合中对名字进行分组。​​lastname​​​​Person​​​​PersonAggregate​

if参数存在,附加到声明的管道阶段之后,以便它只影响通过所有其他聚合阶段后最终结果的顺序。 因此,属性被映射到方法返回类型这 turntowhyis 被注释。​​Sort​​​​$sort​​​​Sort​​​​PersonAggregate​​​​Sort.by("lastname")​​​​{ $sort : { '_id', 1 } }​​​​PersonAggregate.lastname​​​​@Id​

替换为动态聚合管道的给定值。​​?0​​​​property​

​$skip​​​,并且可以通过参数传递。与 <2> 中相同,运算符将追加到管道定义中。接受方法可以返回以便于分页。​​$limit​​​​$sort​​​​Pageable​​​​Pageable​​​​Slice​

聚合方法可以返回以直接从基础游标使用结果。请确保在使用流后关闭流,以通过调用 or 通过释放服务器端游标。​​Stream​​​​close()​​​​try-with-resources​

将返回单个的聚合的结果映射到所需目标类型的实例。​​Document​​​​SumValue​

聚合导致单个文档仅包含累积结果,例如可以直接从结果中提取。 若要获得更多控制,可以考虑方法 返回类型,如 <7> 所示。​​$sum​​​​Document​​​​AggregationResult​

获取到通用目标包装器类型化的原始映射。​​AggregationResults​​​​SumValue​​​​org.bson.Document​

与 <6> 一样,可以从多个结果中直接获得单个值。​​Document​

在某些情况下,聚合可能需要其他选项,例如最长运行时间、其他日志注释或将数据临时写入磁盘的权限。 使用注释通过,或设置这些选项。​​@Meta​​​​maxExecutionTimeMs​​​​comment​​​​allowDiskUse​

interface PersonRepository extends CrudReppsitory<Person, String> {

@Meta(allowDiskUse = true)
@Aggregation("{ $group: { _id : $lastname, names : { $addToSet : $firstname } } }")
List<PersonAggregate> groupByLastnameAndFirstnames();
}

或者用于创建自己的注释,如以下示例所示。​​@Meta​

@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.METHOD })
@Meta(allowDiskUse = true)
@interface AllowDiskUse { }

interface PersonRepository extends CrudReppsitory<Person, String> {

@AllowDiskUse
@Aggregation("{ $group: { _id : $lastname, names : { $addToSet : $firstname } } }")
List<PersonAggregate> groupByLastnameAndFirstnames();
}



简单类型单一结果检查返回的结果并检查以下内容:​​Document​



  1. 文档中只有一个条目,返回它。
  2. 两个条目,一个是值。返回另一个。​​_id​
  3. 返回可分配给返回类型的第一个值。
  4. 如果上述情况都不适用,则引发异常。


使用存储库方法不支持 Thereturn 类型。但是,您可以使用参数将 and,and添加到管道并让方法返回。​​Page​​​​@Aggregation​​​​Pageable​​​​$skip​​​​$limit​​​​$sort​​​​Slice​

14.3. CDI 集成

存储库接口的实例通常由容器创建,在处理 Spring 数据时,Spring 是最自然的选择。 从版本1.3.0开始,Spring Data MongoDB附带了一个自定义CDI扩展,允许您在CDI环境中使用存储库抽象。 扩展是 JAR 的一部分。 要激活它,请将 Spring Data MongoDB JAR 放入您的类路径中。 现在,您可以通过实现 CDI 创建器来设置基础结构,如以下示例所示:​​MongoTemplate​

class MongoTemplateProducer {

@Produces
@ApplicationScoped
public MongoOperations createMongoTemplate() {

MongoDatabaseFactory factory = new SimpleMongoClientDatabaseFactory(MongoClients.create(), "database");
return new MongoTemplate(factory);
}
}

Spring Data MongoDB CDI 扩展选取可用作为 CDI bean,并在容器请求存储库类型的 Bean 时为 Spring Data 存储库创建代理。 因此,获取 Spring 数据存储库的实例是声明 an-ed 属性的问题,如以下示例所示:​​MongoTemplate​​​​@Inject​

class RepositoryClient {

@Inject
PersonRepository repository;

public void businessMethod() {
List<Person> people = repository.findAll();
}
}

15. 反应式 MongoDB 存储库

本章介绍 MongoDB 反应式存储库支持的专业。本章建立在使用 Spring 数据存储库中解释的核心存储库支持之上。您应该对那里解释的基本概念有很好的理解。

15.1. 反应式合成库

反应空间提供各种反应式合成库。最常见的库是RxJava和Project Reactor。

Spring Data MongoDB建立在MongoDB反应流驱动程序之上,通过依赖反应式流计划提供最大的互操作性。静态 API(例如)是通过使用 Project Reactor 的沙型提供的。Project Reactor 提供了各种适配器来转换反应式包装器类型(反之亦然),但转换很容易使您的代码混乱。​​ReactiveMongoOperations​​​​Flux​​​​Mono​​​​Flux​​​​Observable​

Spring Data 的存储库抽象是一个动态 API,主要由您和您的需求在声明查询方法时定义。反应式MongoDB存储库可以通过从以下特定于库的存储库接口之一扩展,使用RxJava或Project Actor包装器类型来实现:

  • ​ReactiveCrudRepository​
  • ​ReactiveSortingRepository​
  • ​RxJava2CrudRepository​
  • ​RxJava2SortingRepository​
  • ​RxJava3CrudRepository​
  • ​RxJava3SortingRepository​

Spring Data 在幕后转换反应式包装器类型,以便您可以坚持使用自己喜欢的合成库。

15.2. 用法

要访问存储在MongoDB数据库中的域实体,您可以使用我们复杂的存储库支持,这大大简化了这些实现。为此,请为您的存储库创建一个类似的界面。但是,在执行此操作之前,您需要一个实体,例如以下示例中定义的实体:

例 160.样本实体​​Person​

public class Person {

@Id
private String id;
private String firstname;
private String lastname;
private Address address;

// … getters and setters omitted
}

请注意,前面示例中定义的实体具有名为 namedof type 的属性。中使用的默认序列化机制(支持存储库支持)将属性命名为文档 ID。 目前,我们支持,和作为 id 类型。 有关如何在映射图层中处理字段的详细信息,请参阅ID 映射。​​id​​​​String​​​​MongoTemplate​​​​id​​​​String​​​​ObjectId​​​​BigInteger​​​​id​

下面的示例演示如何创建一个接口,该接口定义针对上述示例中对象的查询:​​Person​

例 161.用于保留人员实体的基本存储库接口

public interface ReactivePersonRepository extends ReactiveSortingRepository<Person, String> {

Flux<Person> findByFirstname(String firstname);

Flux<Person> findByFirstname(Publisher<String> firstname);

Flux<Person> findByFirstnameOrderByLastname(String firstname, Pageable pageable);

Mono<Person> findByFirstnameAndLastname(String firstname, String lastname);

Mono<Person> findFirstByLastname(String lastname);
}

该方法显示所有给定人员的查询。查询是通过分析可与 and 连接的约束的方法名称派生的。因此,方法名称导致查询表达式。​​lastname​​​​And​​​​Or​​​​{"lastname" : lastname}​

该方法显示所有具有给定一次的查询的人是由给定的发出的。​​firstname​​​​firstname​​​​Publisher​

用于将偏移量和排序参数传递给数据库。​​Pageable​

查找给定条件的单个实体。它以非唯一结果完成。​​IncorrectResultSizeDataAccessException​

除非 <4>,否则即使查询生成更多结果文档,也始终发出第一个实体。

对于 Java 配置,请使用注释。注释具有与命名空间元素相同的属性。如果未配置基本包,基础结构将扫描带批注的配置类的包。​​@EnableReactiveMongoRepositories​

MongoDB使用两种不同的驱动程序进行命令式(同步/阻塞)和反应式(非阻塞)数据访问。您必须使用 Reactive Streams 驱动程序创建连接,以便为 Spring Data 的 Reactive MongoDB 支持提供所需的基础结构。因此,您必须为 MongoDB 的反应式流驱动程序提供单独的配置。请注意,如果您使用反应式和阻塞性 Spring Data MongoDB 模板和存储库,您的应用程序将在两个不同的连接上运行。

以下清单显示了如何将 Java 配置用于存储库:

例 162。存储库的 Java 配置

@Configuration
@EnableReactiveMongoRepositories
class ApplicationConfig extends AbstractReactiveMongoConfiguration {

@Override
protected String getDatabaseName() {
return "e-store";
}

@Override
public MongoClient reactiveMongoClient() {
return MongoClients.create();
}

@Override
protected String getMappingBasePackage() {
return "com.oreilly.springdata.mongodb";
}
}

由于我们的域存储库可扩展,因此它为您提供了 CRUD 操作以及对实体进行排序访问的方法。使用存储库实例是将其注入客户端的依赖项问题,如以下示例所示:​​ReactiveSortingRepository​

例 163。对个人实体的排序访问

@ExtendWith(SpringExtension.class)
@ContextConfiguration
class PersonRepositoryTests {

@Autowired ReactivePersonRepository repository;

@Test
public void sortsElementsCorrectly() {
Flux<Person> persons = repository.findAll(Sort.by(new Order(ASC, "lastname")));
}
}

反应式存储库不支持 Thereturn 类型(如 in)。​​Page​​​​Mono<Page>​

可以使用派生的查找器方法,将参数传递给查询,以减少负载和网络流量。 返回的将仅发出声明范围内的数据。​​Pageable​​​​sort​​​​limit​​​​offset​​​​Flux​

例 164.使用反应式存储库限制和抵消

Pageable page = PageRequest.of(1, 10, Sort.by("lastname"));
Flux<Person> persons = repository.findByFirstnameOrderByLastname("luke", page);

15.3. 功能

Spring Data的Reactive MongoDB支持与阻塞MongoDB存储库相比具有更少的功能集。

它支持以下功能:

  • 使用字符串查询和查询派生的查询方法
  • 地理空间存储库查询
  • 存储库删除查询
  • 基于 MongoDB JSON 的查询方法和字段限制
  • 全文搜索查询
  • 类型安全的查询方法
  • 预测

15.3.1. 地理空间存储库查询

正如您之前在“地理空间存储库查询”中看到的那样,一些关键字会在MongoDB查询中触发地理空间操作。关键字允许进一步修改,如下几个示例所示。​​Near​

下面的示例演示如何定义查找给定点给定距离的所有人员的查询:​​near​

例 165.高级查询​​Near​

interface PersonRepository extends ReactiveMongoRepository<Person, String> {

// { 'location' : { '$near' : [point.x, point.y], '$maxDistance' : distance}}
Flux<Person> findByLocationNear(Point location, Distance distance);
}

向查询方法添加参数允许将结果限制为给定距离内的结果。如果设置包含 a,我们透明地使用 代替,如以下示例所示:​​Distance​​​​Distance​​​​Metric​​​​$nearSphere​​​​$code​

例 166.使用与​​Distance​​​​Metrics​

Point point = new Point(43.7, 48.8);
Distance distance = new Distance(200, Metrics.KILOMETERS);
… = repository.findByLocationNear(point, distance);
// {'location' : {'$nearSphere' : [43.7, 48.8], '$maxDistance' : 0.03135711885774796}}

反应式地理空间存储库查询支持反应式包装器类型中的域类型和结果。并且不受支持,因为它们与预先计算平均距离的延迟结果方法相矛盾。但是,您仍然可以自己将参数传递给页面结果。​​GeoResult<T>​​​​GeoPage​​​​GeoResults​​​​Pageable​

使用 awith 会导致添加(而不是普通)子句。除此之外,实际距离将根据使用量计算。​​Distance​​​​Metric​​​​$nearSphere​​​​$near​​​​Metrics​

(请注意,这不是指公制度量单位。它可能是英里而不是公里。相反,指的是测量系统的概念,无论您使用哪种系统。​​Metric​​​​metric​

在目标属性上使用强制使用运算符。​​@GeoSpatialIndexed(type = GeoSpatialIndexType.GEO_2DSPHERE)​​​​$nearSphere​

地理邻近查询

Spring Data MongoDB支持地理邻近查询,如以下示例所示:

interface PersonRepository extends ReactiveMongoRepository<Person, String>  {

// {'geoNear' : 'location', 'near' : [x, y] }
Flux<GeoResult<Person>> findByLocationNear(Point location);

// No metric: {'geoNear' : 'person', 'near' : [x, y], maxDistance : distance }
// Metric: {'geoNear' : 'person', 'near' : [x, y], 'maxDistance' : distance,
// 'distanceMultiplier' : metric.multiplier, 'spherical' : true }
Flux<GeoResult<Person>> findByLocationNear(Point location, Distance distance);

// Metric: {'geoNear' : 'person', 'near' : [x, y], 'minDistance' : min,
// 'maxDistance' : max, 'distanceMultiplier' : metric.multiplier,
// 'spherical' : true }
Flux<GeoResult<Person>> findByLocationNear(Point location, Distance min, Distance max);

// {'geoNear' : 'location', 'near' : [x, y] }
Flux<GeoResult<Person>> findByLocationNear(Point location);
}

15.3.2. 类型安全的查询方法

Reactive MongoDB 存储库支持与Querydsl项目集成,该项目提供了一种执行类型安全查询的方法。

它们不是将查询编写为内联字符串或将其外部化为 XML 文件,而是通过流畅的 API 构造的。

— 查询团队

它提供以下功能:

  • 在 IDE 中完成代码(所有属性、方法和操作都可以在您喜欢的 Java IDE 中展开)。
  • 几乎不允许语法无效的查询(所有级别的类型安全)。
  • 可以安全地引用域类型和属性 - 不涉及字符串!
  • 更好地适应域类型的重构更改。
  • 增量查询定义更容易。

请参阅Querydsl 文档,了解如何使用 Maven 或 Ant 引导环境以进行基于 APT 的代码生成。

Querydsl 存储库支持允许您编写和运行查询,如下所示:

QPerson person = QPerson.person;

Flux<Person> result = repository.findAll(person.address.zipCode.eq("C0123"));

​QPerson​​是由 Java 注解后处理工具生成的类。它允许您编写类型安全的查询。 请注意,查询中没有除值之外的字符串。​​Predicate​​​​C0123​

您可以通过接口来使用生成的类,以下清单显示了该接口:​​Predicate​​​​ReactiveQuerydslPredicateExecutor​

例 167.Reactive Querydsl 的网关 - ReactiveQuerydslPredicateExecutor

interface ReactiveQuerydslPredicateExecutor<T> {

Mono<T> findOne(Predicate predicate);

Flux<T> findAll(Predicate predicate);

Flux<T> findAll(Predicate predicate, Sort sort);

Flux<T> findAll(Predicate predicate, OrderSpecifier<?>... orders);

Flux<T> findAll(OrderSpecifier<?>... orders);

Mono<Long> count(Predicate predicate);

Mono<Boolean> exists(Predicate predicate);
}

要在存储库实现中使用它,请将其添加到接口继承的存储库接口列表中,如以下示例所示:

例 168.反应式查询 Respository 声明

interface PersonRepository extends ReactiveMongoRepository<Person, String>, ReactiveQuerydslPredicateExecutor<Person> {

// additional query methods go here
}

请注意,Reactive MongoDB 支持不支持连接 (DBRef)。

Spring Data (数据)MongoDB(三)

16. 审计

16.1. 基础知识

Spring Data提供了复杂的支持,可以透明地跟踪谁创建或更改了实体以及更改发生的时间。若要从该功能中受益,必须为实体类配备可以使用批注或通过实现接口来定义的审核元数据。 此外,必须通过注释配置或 XML 配置启用审核,以注册所需的基础结构组件。 有关配置示例,请参阅特定于商店的部分。


不需要仅跟踪创建和修改日期的应用程序会使其实体实现AuditorAware。


16.1.1. 基于注释的审计元数据

我们提供捕获创建或修改实体的用户以及何时发生更改的捕获。​​@CreatedBy​​​​@LastModifiedBy​​​​@CreatedDate​​​​@LastModifiedDate​

例 169.被审计的实体

class Customer {

@CreatedBy
private User user;

@CreatedDate
private Instant createdDate;

// … further properties omitted
}

如您所见,注释可以有选择地应用,具体取决于要捕获的信息。 指示在进行更改时捕获的注释可用于 JDK8 日期和时间类型,,,以及旧版 Javaand 的属性。​​long​​​​Long​​​​Date​​​​Calendar​

审核元数据不一定需要位于根级实体中,但可以添加到嵌入的实体中(取决于使用的实际存储),如下面的代码片段所示。

例 170.审核嵌入实体中的元数据

class Customer {

private AuditMetadata auditingMetadata;

// … further properties omitted
}

class AuditMetadata {

@CreatedBy
private User user;

@CreatedDate
private Instant createdDate;

}

16.1.2. 基于接口的审计元数据

如果您不想使用注释来定义审核元数据,则可以让您的域类实现接口。它公开所有审核属性的 setter 方法。​​Auditable​

16.1.3. ​​AuditorAware​

如果使用任一 or,则审计基础结构需要以某种方式了解当前主体。为此,我们提供了 anSPI 接口,您必须实现该接口来告诉基础架构当前与应用程序交互的用户或系统是谁。泛型类型定义批注属性的类型。​​@CreatedBy​​​​@LastModifiedBy​​​​AuditorAware<T>​​​​T​​​​@CreatedBy​​​​@LastModifiedBy​

以下示例显示了使用 Spring 安全性对象的接口的实现:​​Authentication​

例 171.基于弹簧安全性的实现​​AuditorAware​

class SpringSecurityAuditorAware implements AuditorAware<User> {

@Override
public Optional<User> getCurrentAuditor() {

return Optional.ofNullable(SecurityContextHolder.getContext())
.map(SecurityContext::getAuthentication)
.filter(Authentication::isAuthenticated)
.map(Authentication::getPrincipal)
.map(User.class::cast);
}
}

该实现访问 Spring 安全性提供的对象,并查找您在实现中创建的自定义实例。我们在这里假设您通过实现公开域用户,但基于发现,您也可以从任何地方查找它。​​Authentication​​​​UserDetails​​​​UserDetailsService​​​​UserDetails​​​​Authentication​

16.1.4. ​​ReactiveAuditorAware​

使用响应式基础结构时,您可能希望利用上下文信息来提供信息。 我们提供 anSPI 接口,您必须实现该接口来告诉基础架构当前与应用程序交互的用户或系统是谁。泛型类型定义批注属性的类型。​​@CreatedBy​​​​@LastModifiedBy​​​​ReactiveAuditorAware<T>​​​​T​​​​@CreatedBy​​​​@LastModifiedBy​

以下示例显示了使用反应式 Spring 安全性对象的接口的实现:​​Authentication​

例 172.基于弹簧安全性的实现​​ReactiveAuditorAware​

class SpringSecurityAuditorAware implements ReactiveAuditorAware<User> {

@Override
public Mono<User> getCurrentAuditor() {

return ReactiveSecurityContextHolder.getContext()
.map(SecurityContext::getAuthentication)
.filter(Authentication::isAuthenticated)
.map(Authentication::getPrincipal)
.map(User.class::cast);
}
}

该实现访问 Spring 安全性提供的对象,并查找您在实现中创建的自定义实例。我们在这里假设您通过实现公开域用户,但基于发现,您也可以从任何地方查找它。​​Authentication​​​​UserDetails​​​​UserDetailsService​​​​UserDetails​​​​Authentication​

16.2. MongoDB 的一般审计配置

从Spring Data MongoDB 1.4开始,可以通过使用注释注释配置类来启用审计,如以下示例所示:​​@EnableMongoAuditing​

爪哇岛

.XML

@Configuration
@EnableMongoAuditing
class Config {

@Bean
public AuditorAware<AuditableUser> myAuditorProvider() {
return new AuditorAwareImpl();
}
}

如果公开 typeto 的 bean,则审核基础结构会自动选取它,并使用它来确定要在域类型上设置的当前用户。如果在中注册了多个实现,则可以通过显式设置属性来选择要使用的一个。​​AuditorAware​​​​ApplicationContext​​​​ApplicationContext​​​​auditorAwareRef​​​​@EnableMongoAuditing​

若要启用审核,请利用响应式编程模型,请使用注释。
如果公开 typeto 的 bean,则审核基础结构会自动选取它,并使用它来确定要在域类型上设置的当前用户。如果在中注册了多个实现,则可以通过显式设置属性来选择要使用的一个。​​@EnableReactiveMongoAuditing​​​​ReactiveAuditorAware​​​​ApplicationContext​​​​ApplicationContext​​​​auditorAwareRef​​​​@EnableReactiveMongoAuditing​

例 173.使用 JavaConfig 激活反应式审计

@Configuration
@EnableReactiveMongoAuditing
class Config {

@Bean
public ReactiveAuditorAware<AuditableUser> myAuditorProvider() {
return new AuditorAwareImpl();
}
}