春季数据MongoDB save()结果双重插入
我们遇到了以下行为,但我们希望知道它是否是预期的,以及是否有兴趣将它记录为一些有种陷阱。春季数据MongoDB save()结果双重插入
我们与春天启动2 /春WebFlux试验和设置了一个小的应用程序,主要有这样的事情(所有缩短):
@PostMapping
public Mono<Todo> addTodos(@RequestBody Person person) {
return personService.addPerson(person);
}
服务第一这个样子,因为我们希望还出版人另外的事件到消息队列:
public class PersonService {
public Mono<Person> addPerson(Person person) {
Mono<Person> addedPerson = personRepository.save(person);
addedPerson.subscribe(p -> rabbitTemplate.convertAndSend("persons", p));
return addedPerson;
}
}
所以,这显然是错误的,那样做。 .subscribe()
触发流程,我们假设反应REST控制器在序列化响应数据之前在后台执行相同操作,从而产生第二个并行流程。最后,我们在数据库中的persons
集合中结束了两个重复条目。
经过这么长时间的介绍后,终于提出了这样一个问题:这是多个用户触发多个插入的预期行为(基本上,如果您订阅n
次,您将得到n
插入)?
如果是,这可能是初学者需要强调的一个陷阱,特别是如果我们的理解是正确的,那么反应式REST控制器会在屏幕下执行.subscribe()
。
你来到了描述预期行为的结论。
反应式编程模型与各个领域的命令式编程模型不同。
命令式编程结合了转换,映射,执行和其他方面。您可以通过创建条件/循环流,可以返回值并将值传递给API调用的方法调用来表达这些。
反应式编程解耦的什么从它是如何将要执行的发生的声明。使用反应式基础设施的执行分为两部分:反应式序列组成和实际执行。在你的代码中,你只编写反应序列。执行发生在您的代码之外。
当您撰写Publisher
时,则生成的Publisher
包含对执行后会发生的事情的声明。 A Publisher
并不意味着它是否将首先执行,也不意味着最终订阅的订阅者数量。
从上方拍摄的例子中,Mono<Person> PersonRepository.save(…)
返回一个发布商:从Person
到Document
- 地图数据保存
Document
到的MongoDB和 - 发射保存
Person
一旦从MongoDB的一个响应来返回
这是一个使用特定存储库保存数据的配方方法。创建发布者不会执行发布者,发布者也不会对执行次数置之不理。多次拨打.subscribe()
多次执行发布者。我想说.subscribe()
不是一个陷阱。一种反应式编程模型方法将您的执行放在了一边。如果您拨打.subscribe()
或.block()
,那么您应该有充分的理由这样做。每当您在代码中看到.subscribe()
或.block()
时,您应该特别注意这是否正确。您的执行环境负责订阅Publisher
。
几个观察结果:
-
RabbitTemplate
是阻塞API。你不应该混合反应和阻塞的API。如果您没有其他选择,则将阻止呼叫转移给工作人员。在包含阻塞工作的实际运营商之前,沿着Scheduler
使用publishOn(…)
,或者使用ExecutorService
/CompletableFuture
以及flatMap(…)
。 - 使用
flatMap(…)
运营商的反应流程组成Mono
/Flux
。flatMap(…)
操作员启动最终完成并继续流程的非阻塞子流程。 - 使用
doOnXXX(…)
运营商(doOnNext(…)
,doOnSuccess(…)
,...)为回发时发布者发出特定信号。这些钩子方法允许方便地拦截非阻塞消耗的元素。
参考文献:
非常感谢那精心设计的答案!我的关键是“如果你调用'subscribe()''你应该有一个很好的理由”。我并不是全新的反应式编程,但项目反应器中的术语和最佳实践对我来说是全新的。 也感谢操作员概述!不知怎的,错过了。 –