在Spring Data上订阅两次MongoDB save()会导致双重插入

时间:2017-07-25 06:23:27

标签: spring spring-mvc spring-data-mongodb reactive project-reactor

我们遇到了以下我们理解的行为,但是我们想知道这是否是预期的,以及将其记录为某种陷阱可能是有意义的。

我们正在尝试使用Spring Boot 2 / Spring WebFlux并设置一个基本上具有类似功能的小应用程序(全部缩短):

h

服务首先看起来像这样,因为我们想要将一个人的事件也发布到消息队列中:

@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; } } 触发流程,我们假设响应式REST控制器在序列化响应数据之前在后台执行相同操作,从而产生第二个并行流。最后,我们最终在数据库的.subscribe()集合中有两个重复的条目。

在这个冗长的介绍之后,最后的问题是:这是多个订阅者触发多个插入的预期行为(基本上,如果您订阅persons次,则会获得n次插入)?

如果是,这对初学者来说可能是一个陷阱,特别是如果我们的理解是正确的,那么反应式REST控制器会在引擎盖下执行n

1 个答案:

答案 0 :(得分:5)

你得出的结论描述了预期的行为。

反应式编程模型与各种领域的命令式编程模型不同。

命令式编程结合了转换,映射,执行和其他方面。您可以通过创建条件/循环流,方法调用来表达这些,这些方法调用可以返回值并将值传递给API调用。

反应式编程将 正在发生什么的声明与 将要执行的内容分离。使用反应式基础设施的执行分为两部分:反应序列组成和实际执行。在您的代码中,您只编写反应序列。执行发生在您的代码之外。

撰写Publisher时,生成的Publisher包含如果执行将会发生的事情的声明。 Publisher并不意味着它是否会首先执行,也不会最终订阅多少订阅者。

从上面的示例中,Mono<Person> PersonRepository.save(…)返回的发布商:

  1. 将数据从Person映射到Document
  2. Document保存到MongoDB和
  3. 一旦MongoDB的响应回来,就会发出保存的Person
  4. 这是使用特定存储库方法保存数据的方法。创建发布者不会执行发布者,并且发布者不会就执行次数发表意见。多次调用.subscribe()多次执行发布商。

    我认为.subscribe()不是一个陷阱。反应式编程模型方法会使执行失败。如果您致电.subscribe().block(),那么您应该有充分的理由这样做。每当您在代码中看到.subscribe().block()时,您应该特别注意这是否正确。您的执行环境负责订阅Publisher

    一些观察结果:

    • RabbitTemplate是一个阻止API。您不应混合使用反应和阻止API。如果您没有其他选项,则卸载阻止对工作人员的呼叫。在包含阻止作品的实际操作符之前的publishOn(…)上使用Scheduler,或者使用ExecutorService / CompletableFutureflatMap(…)
    • flatMap(…)运算符用于Mono / Flux的反应流组合。 flatMap(…)运算符启动最终完成的非阻塞子进程并继续流程。
    • 当发布者发出特定信号时,使用doOnXXX(…)运算符(doOnNext(…)doOnSuccess(…),...)进行回调。这些钩子方法可以方便地拦截元素非阻塞消耗。

    参考文献: