【问题标题】:Spring Transaction Annotations - Execute on SuccessSpring Transaction Annotations - 成功执行
【发布时间】:2010-08-16 10:37:58
【问题描述】:

我在我的服务层中使用 Spring 应用程序事件来在特定事件发生时通知更广泛的系统。我遇到的问题是事件是在事务上下文中触发的(我使用的是 Spring 的 @Transactional 注释)。危险是我触发了一个事件,然后事务在提交时失败(例如,在使用 Hibernate 时可能发生)。在这种情况下,事件侦听器将假设某些状态,然后在它们执行后回滚。这里的危险是,例如,我可能已经发送了一封电子邮件来确认用户在网站上的注册,而实际上他们的用户帐户并未真正创建。

有没有办法保留注释的使用,在某处标记事务提交后要触发的事件?这有点类似于在 Java Swing 中进行 GUI 编程时使用SwingUtilities.invokeLater(Runnable runnable)。我想说,一旦当前事务成功提交,稍后再执行这段代码。

有什么想法吗?

谢谢,

安德鲁

【问题讨论】:

  • 这一切现在都在 Spring 中通过注释本地处理。请参阅spring.io/blog/2015/02/11/…,特别是有关新的@TransactionalEventListener 注释的部分。

标签: spring transactions


【解决方案1】:

这对我有用:

TransactionSynchronizationManager.registerSynchronization(
    new TransactionSynchronizationAdapter() {
        @Override
        public void afterCommit() {
            // things to do when commited
        }
        // other methods to override are available too
    });

【讨论】:

  • 不错!关于问题 IMO 的最佳答案。这与 SwingUtilities.invokeLater 最相似。
  • @oliv 这个问题是在对这段代码进行单元测试时出现的。
【解决方案2】:

您可以使用 TransactionSynchronizationManager 而无需破解 PlatformTransactionManager。

注意:TransactionAware 是一个标记接口,表示 ApplicationListener 想要在事务提交成功后接收 Event。

public class TransactionAwareApplicationEventMulticaster extends SimpleApplicationEventMulticaster {

    @Override
    public void multicastEvent(ApplicationEvent event) {
        for (ApplicationListener listener : getApplicationListeners(event)) {
            if ((listener instanceof TransactionAware) && TransactionSynchronizationManager.isSynchronizationActive()) {
                TransactionSynchronizationManager.registerSynchronization(
                    new EventTransactionSynchronization(listener, event));
            }
            else {
                notifyEvent(listener, event);
            }
        }
     }

     void notifyEvent(final ApplicationListener listener, final ApplicationEvent event) {
          Executor executor = getTaskExecutor();
          if (executor != null) {
               executor.execute(new Runnable() {
                    public void run() {
                         listener.onApplicationEvent(event);
                    }
               });
          }
          else {
               listener.onApplicationEvent(event);
          }
     }

    class EventTransactionSynchronization extends TransactionSynchronizationAdapter {
        private final ApplicationListener listener;
        private final ApplicationEvent event;

        EventTransactionSynchronization(ApplicationListener listener, ApplicationEvent event) {
            this.listener = listener;
            this.event = event;
        }

        @Override
        public void afterCompletion(int status) {
            if ((phase == TransactionPhase.AFTER_SUCCESS)
                    && (status == TransactionSynchronization.STATUS_COMMITTED)) {
                notifyEvent(listener, event);
            }
        }
    }
}

【讨论】:

    【解决方案3】:

    解决确认电子邮件问题的正确方法可能是使用分布式事务和参与其中的 JMS 资源。

    但是,作为一种快速而简单的解决方案,您可以尝试在 PlatformTransactionManager 周围创建一个包装器,该包装器将在成功提交后执行在某些 ThreadLocal 存储中注册的 Runnables(并将其从 @987654325 中删除) @回滚后)。其实AbstractPlatformTransactionManagerTransactionSynchronizations也有这种逻辑,只是它的实现细节埋得太深了。

    【讨论】:

    • 我现在已经采用了快速而肮脏的解决方案。我构建了一个基于 ThreadLocal 的机制,它将 Runnable 对象队列与 TransactionStatus 对象相关联。在 TransactionStatus 的 commit() 或 rollback() 上,我要么执行 Runnable 对象,要么丢弃它们。它工作得很好。
    • 我不会说分布式事务是正确的正确解决方案。如果仅发送邮件失败,分布式事务将导致整个事务失败。但是这里期望的行为是邮件依赖于 DB-transaction 而 DB-transaction 不依赖于邮件。
    猜你喜欢
    • 2019-11-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-11-29
    • 2021-11-09
    • 1970-01-01
    相关资源
    最近更新 更多