繁体   English   中英

Spring 多个事务与 TransactionManager(JMS,数据库)

[英]Spring multiple transactions with TransactionManager (JMS, database)

我有一个方法,我想执行两个事务:一个使用 DB,一个使用 JMS。 我希望一个接一个地提交。 我正在尝试为此使用PlatformTransactionManager 有两种方法可以做到这一点:使用TransactionTemplateDefaultTransactionDefinition 但是我没有找到任何一个多次使用的例子。 我想做的是:

void do(){
 T dbTransaction = ...; // here goes: new TransactionTemplate(transactionManager) two times
 T jmsTransaction = ...; // or: new DefaultTransactionDefinition() and transactionManager.getTransaction(definition); two times
 saveDb();
 sendJms();
 dbTransaction.commit();
 jmsTransaction.commit();
}

但我不确定要使用什么以及如何使用,因为在这篇文章中它说:

无论如何,一旦我们创建了一个带有配置的 TransactionTemplate,所有事务都将使用该配置来执行。 所以,如果我们需要多个配置,我们应该创建多个模板实例。

那么如何正确创建两个事务并一个接一个地关闭呢? 我应该创建两个单独的definitions还是可以重复使用一个? 我可以在两个templates中重复使用相同的transactionManager吗? 我知道 DB 有一个@Transcational注释,我也可以将 JMS 配置为使用事务,但是:

  1. 我没有找到如何配置 JMS 以使用事务的好示例
  2. 我不确定他们将在哪个订单中关闭

所以我想手动做到这一点。 我也不确定这个手动事务是否适用于 JMS(例如 IBM-MQ),因为我只看到了数据库事务的示例。

目前尚不清楚为什么您希望在这种特殊情况下使用 JMS 事务,我什至会反对它——至少正如您在上面介绍的那样。

一旦 state 成功存储到数据库中,您基本上想发布一条消息。

既然您的事实来源是数据库,为什么不将所有后续操作都建立在该操作成功完成的基础上呢?

例如,构建它的一种方法是(面向 Spring,因为您已经提到您正在使用它):

  1. 创建一个作用于当前事务的JmsSender bean。 这可以通过实现BeanFactoryPostProcessor并执行以下操作来完成:
    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        SimpleTransactionScope transactionScope = new SimpleTransactionScope();
        // The scope exists, but is not registered by Spring by default.
        beanFactory.registerScope("transaction", transactionScope);
    }

    // in a separate configuration class defining your JmsSender bean
    @Bean
    @Scope("transaction")
    public JmsSender jmsSender() { return new JmsSender(); }
  1. 每次调用此 bean 的send()方法时,都会将一条消息添加到内部队列中。 这通常是一个ThreadLocal<List<T>> - 事实上,Spring 处理事务管理的方式几乎相同。
  2. 创建一个作为TransactionSynchronizationAdapterAfterCommitJmsPublisher bean - 这意味着我们希望在提交时有额外的行为。
  3. 注册AfterCommitJmsPublisher 这意味着在事务之前调用TransactionSynchronizationManager.registerSynchronization(jmsPublisher) 使用例如方面、声明性事务管理 ( @Transactional ) 和 Spring AOP 的一种方法是:
@Aspect
@Component
public class AfterCommitJmsPublisher extends TransactionSynchronizationAdapter {

    private final JmsPublisher;

    @Pointcut("@annotation(org.springframework.transaction.annotation.Transactional)")
    private void transactionalPointcut() {
    }

    @Before("transactionalPointcut()")
    public void registerTransactionSynchronization() {
        TransactionSynchronizationManager.registerSynchronization(this);
    }
  1. 提交数据库事务后,调用类似jmsPublisher.publish()的方法。 这可以在TransactionSynchronizationAdapterafterCommit()方法中完成:
    // In AfterCommitJmsPublisher
    @Override
    public void afterCommit() {
        jmsPublisher.publish();
    }
  1. 如果事务回滚,则调用类似jmsPublisher.clear()的方法。 您可能不想发布任何有关失败操作的消息。

这样,您的 JMS 消息始终绑定到它们所源自的事务 - 如果数据库事务失败,则不会发送任何消息。

离开你的评论:

在手动场景中,如果 JMS 失败,它将失败,但如果 JMS 没有异常,则将其保存到 DB,即使在与 JMS 的事务失败之后我也可以接受,因为我将 state 保存在 DB 中。

这可能足以满足您的要求。 但是,您可能需要考虑到,您拥有的组件越多,系统需要的容错能力就越高,并考虑到可能不可用的外部服务。

这可能意味着将 JMS 消息保存在一个特殊的数据库表中(作为事务的一部分),并且仅在成功提交后发布。 成功发布后删除保存的消息,如果不成功。 您可以实现一个管家任务,重新尝试发布您的消息。

最后,关于分布式事务的一句话:我个人建议尽可能不要使用它们,尤其是对于您当前的用例。 它们是复杂的野兽,几乎肯定会影响应用程序的可用性并增加事务中涉及的所有进程的端到端延迟。 Saga 模式这样的东西通常更适合分布式系统。

当然,这可能不适用于您的用例,并且您的一致性要求可能超过任何可用性要求,因此请谨慎对待。

您的用例简单而常见。 您希望发送一条 JMS 消息,等待该消息完成,然后提交到数据库。 这是在两个事务上完成的——一个用于 JMS 消息,另一个用于数据库。 这些事务都存在于单个事务上下文中。 当您启动 JMS 事务时,将不存在事务上下文,因此将创建一个。 当您启动数据库事务时,它将加入现有的事务上下文。 这些事务将被同步,因为 JMS 事务必须在数据库事务提交之前成功完成。

此操作的核心是事务管理器。 查看您链接的文章,他们对PlatformTransactionManager做了很多引用。 在您的用例中, PlatformTransactionManager必须是支持 JTA 的事务管理器。 JTA 事务管理器将能够创建事务上下文并注册和同步事务。

请注意,这是两个本地事务,这绝不是 XA 或分布式事务。 在这种情况下,如果 JMS 本地事务失败,那么数据库本地事务将被回滚。 更具体地说,只有事务上下文被标记为回滚。 如果发生任何未处理的异常,则事务上下文仅标记为回滚。 对本地事务调用commit()的任何尝试都将失败,并显示一条消息,指出事务上下文仅回滚。

实现这一点取决于平台。 例如,如果您的 Spring 项目部署在 JBoss 等应用服务器上的 WAR 文件中,则PlatformTransactionManager将自动自动装配。 如果您使用的是 Spring Boot,那么大多数配置甚至不包括事务管理器。

我有一个事务性 Spring JMS 和 Camel 用于 Spring 在此处启动。 这是一个用于 IBM MQ 的简单消息桥。 如果不出意外,Spring JMS 和注释以及事务性 IBM MQ 的示例应该很有用。 也许骆驼钻头也很有用。

注意pom.xml文件包含:

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-jta-narayana</artifactId>
    </dependency>

这个 Spring 引导启动器将安装和配置 Arjuna JTA 事务管理器作为PlatformTransactionManager

在我的示例中,我有:

<logger name="com.arjuna" level="TRACE" additivity="false">
    <appender-ref ref="STDOUT" />
</logger>

这为 Arjuna JTA 事务管理器提供了非常好的日志记录。

最重要的是,获取一个配置为PlatformTransactionManager的 JTA 事务管理器。 使用它来创建事务上下文,并在该上下文中拥有两个本地同步事务。

示例项目应该很容易运行。 日志信息非常丰富。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM