【问题标题】:Spring Data MongoDB Lifecycle events: is it possible to always handle them synchronously?Spring Data MongoDB Lifecycle 事件:是否可以始终同步处理它们?
【发布时间】:2019-05-31 08:16:32
【问题描述】:

我正在尝试使用 Spring Data MongoDB 生命周期事件根据 MongoDB 在插入时生成的 ID 计算“保存后”字段。 Reference documentation 对不同的事件“保存前”说如下:

要在对象进入数据库之前对其进行拦截,您可以注册一个覆盖 onBeforeSave 方法的 org.springframework.data.mongodb.core.mapping.event.AbstractMongoEventListener 的子类。调度事件时,将调用您的侦听器并传递域对象和转换后的 com.mongodb.Document。以下示例显示了如何执行此操作:

然后他们提供了一个例子。

我为“保存后”做了类似的事情:

public class MyMongoDbLifecycleListener extends AbstractMongoEventListener<MyModel> {
    @Override
    public void onAfterSave(AfterSaveEvent<MyModel> event) {
        super.onAfterSave(event);

        MyModel model = event.getSource();

        model.computeValueFromTheAssignedId();
    }
}

这在测试中有效,但在生产前测试中,我们偶然发现了一个问题:有时(实际上,经常)在对象从 Repositorysave() 返回的那一刻起,该值并未预先计算方法。

问题是应用程序事件在我们的应用程序中是异步处理的。我们的配置中有以下内容:

<bean id="simpleAsyncTaskExecutor" class="org.springframework.core.task.SimpleAsyncTaskExecutor">
     ...
</bean>
<bean id="applicationEventMulticaster" class="org.springframework.context.event.SimpleApplicationEventMulticaster">
    <property name="taskExecutor" ref="simpleAsyncTaskExecutor" />
    ...
</bean>

SimpleApplicationEventMulticaster 中有以下代码实际执行多播:

public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) {
    ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));
    for (final ApplicationListener<?> listener : getApplicationListeners(event, type)) {
        Executor executor = getTaskExecutor();
        if (executor != null) {
            executor.execute(() -> invokeListener(listener, event));
        }
        else {
            invokeListener(listener, event);
        }
    }
}

所以我们的应用程序上下文异步多播所有事件并在与我们调用save()的线程不同的线程中调用侦听器,因此我们无法保证看到a的效果保存后的监听器。

我很困惑。文档措辞('在...之前拦截一个对象')清楚地表明这发生在严格地在转换/保存/其他之前。但是一个简单(自然)的配置更改会破坏这些保证。我怀疑我做错了什么,但我应该如何正确地做呢?

我现在唯一的想法是编写一个多播器的扩展,它将专门同步处理所有与 MongoDB 相关的事件。这应该可以,但是破坏系统行为应该这么容易吗?

【问题讨论】:

  • 我给出了一个大胆的猜测,但如果你在事务中,对象可能还没有存储在数据库中,并且事件在调用 save 之后但在实际提交之前传播。
  • 谢谢@Mihai,但我们不使用交易。而且事件实际上是在另一个线程中处理的,没有应用任何外部同步,所以没有办法保证顺序(我通过代码调试了这个)。

标签: java spring spring-data spring-data-mongodb


【解决方案1】:

这是SimpleApplicationEventMulticaster 扩展,它允许实现“spring-data-mongodb 事件的同步处理”策略:

public class SystemEventsAreSyncronousMulticaster extends SimpleApplicationEventMulticaster {
    @Override
    public void multicastEvent(ApplicationEvent event, ResolvableType eventType) {
        ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));
        for (final ApplicationListener<?> listener : getApplicationListeners(event, type)) {
            Executor executor = getTaskExecutor();
            if (executor == null || eventMustBeProcessedSynchronously(event)) {
                invokeListener(listener, event);
            } else {
                executor.execute(() -> invokeListener(listener, event));
            }
        }
    }

    private boolean eventMustBeProcessedSynchronously(ApplicationEvent event) {
        return event instanceof MongoMappingEvent;
    }

    private ResolvableType resolveDefaultEventType(ApplicationEvent event) {
        return ResolvableType.forInstance(event);
    }
}

【讨论】:

    【解决方案2】:

    如果需要执行顺序保证,更合适的机制是EntityCallbacks:https://docs.spring.io/spring-data/mongodb/docs/current/reference/html/#entity-callbacks

    它们不依赖于应用程序事件发布,并且保证在加载之前会调用“加载前”回调。

    对于原始问题中的示例,这是使用实体回调的样子:

    public class MyMongoDbLifecycleCallbacks implements AfterSaveCallback<MyModel> {
        @Override
        public MyModel onAfterSave(MyModel entity, Document document, String collection) {
            entity.computeValueFromTheAssignedId();
            return entity;
        }
    }
    

    【讨论】:

      猜你喜欢
      • 2015-11-03
      • 1970-01-01
      • 2012-08-22
      • 2010-09-20
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-12-06
      • 1970-01-01
      相关资源
      最近更新 更多