简体   繁体   English

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

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

I have a method where I want to execute two transactions: one with DB and one with JMS.我有一个方法,我想执行两个事务:一个使用 DB,一个使用 JMS。 And I want one to commit after another.我希望一个接一个地提交。 I'm trying to use PlatformTransactionManager for that.我正在尝试为此使用PlatformTransactionManager There are 2 ways of doing that: using TransactionTemplate or DefaultTransactionDefinition .有两种方法可以做到这一点:使用TransactionTemplateDefaultTransactionDefinition But I didn't find a single example of usage any of that multiple times.但是我没有找到任何一个多次使用的例子。 What I want to do is:我想做的是:

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();
}

But I'm not sure what to use and how, because in this article it says that:但我不确定要使用什么以及如何使用,因为在这篇文章中它说:

Anyway, once we create a TransactionTemplate with a configuration, all transactions will use that configuration to execute.无论如何,一旦我们创建了一个带有配置的 TransactionTemplate,所有事务都将使用该配置来执行。 So, if we need multiple configurations, we should create multiple template instances.所以,如果我们需要多个配置,我们应该创建多个模板实例。

So how do I correctly create two transcations and close one after each other?那么如何正确创建两个事务并一个接一个地关闭呢? Should I create two separate definitions or I can reuse one?我应该创建两个单独的definitions还是可以重复使用一个? Can I reuse same transactionManager in two templates ?我可以在两个templates中重复使用相同的transactionManager吗? I know that there's a @Transcational annotation for DB and that I can configure JMS to use transcations too, but:我知道 DB 有一个@Transcational注释,我也可以将 JMS 配置为使用事务,但是:

  1. I didn't found good examples how to configure JMS for using transactions我没有找到如何配置 JMS 以使用事务的好示例
  2. I'm not sure in which oreder they will close我不确定他们将在哪个订单中关闭

So I want to manually do that.所以我想手动做到这一点。 I'm also not sure that this manual transaction will work with JMS (for example, IBM-MQ) because I only saw examples of transactions for databases.我也不确定这个手动事务是否适用于 JMS(例如 IBM-MQ),因为我只看到了数据库事务的示例。

It's unclear why exactly you wish to use JMS transactions in this particular case and I'd even argue against it - at least as you have presented them above.目前尚不清楚为什么您希望在这种特殊情况下使用 JMS 事务,我什至会反对它——至少正如您在上面介绍的那样。

You basically want to publish a message once the state has been successfully stored to the Database.一旦 state 成功存储到数据库中,您基本上想发布一条消息。

Since your source of truth is the database, why not base all subsequent actions off of that action being successfully completed?既然您的事实来源是数据库,为什么不将所有后续操作都建立在该操作成功完成的基础上呢?

For example, one way to build this would be something like (Spring-oriented since you've mentioned that you're using it):例如,构建它的一种方法是(面向 Spring,因为您已经提到您正在使用它):

  1. Create a JmsSender bean which is scoped to the current transaction.创建一个作用于当前事务的JmsSender bean。 This can be done by implementing a BeanFactoryPostProcessor and doing something like:这可以通过实现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. Every time the send() method of this bean is called, a message is added to an internal queue.每次调用此 bean 的send()方法时,都会将一条消息添加到内部队列中。 This is usually a ThreadLocal<List<T>> - in fact, Spring handles transaction management pretty much the same way.这通常是一个ThreadLocal<List<T>> - 事实上,Spring 处理事务管理的方式几乎相同。
  2. Create a AfterCommitJmsPublisher bean which is a TransactionSynchronizationAdapter - this means that we want additional behaviour on commit.创建一个作为TransactionSynchronizationAdapterAfterCommitJmsPublisher bean - 这意味着我们希望在提交时有额外的行为。
  3. Register the AfterCommitJmsPublisher .注册AfterCommitJmsPublisher This means calling TransactionSynchronizationManager.registerSynchronization(jmsPublisher) prior to the transaction.这意味着在事务之前调用TransactionSynchronizationManager.registerSynchronization(jmsPublisher) One way to do this, using eg Aspects, declarative transaction management ( @Transactional ) and Spring AOP would be:使用例如方面、声明性事务管理 ( @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. When your Database transaction has been committed, call something like jmsPublisher.publish() .提交数据库事务后,调用类似jmsPublisher.publish()的方法。 This can be done in the afterCommit() Method of your TransactionSynchronizationAdapter :这可以在TransactionSynchronizationAdapterafterCommit()方法中完成:
    // In AfterCommitJmsPublisher
    @Override
    public void afterCommit() {
        jmsPublisher.publish();
    }
  1. If the transaction was rolled back, then call something like jmsPublisher.clear() .如果事务回滚,则调用类似jmsPublisher.clear()的方法。 You probably do not want to publish any messages concerning a failed action.您可能不想发布任何有关失败操作的消息。

This way, your JMS messages are always bound to the transaction from which they originated - if the database transaction failed, no messages will be sent.这样,您的 JMS 消息始终绑定到它们所源自的事务 - 如果数据库事务失败,则不会发送任何消息。

Going off your comment:离开你的评论:

In the manual scenario it will fail if JMS failed, but will save it to DB if there's no exception with JMS, and even if after that transaction with JMS will fail I'm ok with that because I'm saving the state in DB.在手动场景中,如果 JMS 失败,它将失败,但如果 JMS 没有异常,则将其保存到 DB,即使在与 JMS 的事务失败之后我也可以接受,因为我将 state 保存在 DB 中。

This would likely suffice for your requirements.这可能足以满足您的要求。 However, you might want to take into account that the more components you have, the more fault-tolerant your system needs to be and account for external services potentially not being available.但是,您可能需要考虑到,您拥有的组件越多,系统需要的容错能力就越高,并考虑到可能不可用的外部服务。

This could mean saving the JMS messages in a special Database table (as part of the transaction,) and only publishing after a successful commit.这可能意味着将 JMS 消息保存在一个特殊的数据库表中(作为事务的一部分),并且仅在成功提交后发布。 deleting the saved messages after a successful publish, If it was unsuccessful.成功发布后删除保存的消息,如果不成功。 you could implement a housekeeper task that reattempts the publication of your messages.您可以实现一个管家任务,重新尝试发布您的消息。

Lastly, a word on distributed transactions: personally I would advise against using them if at all possible, especially for your current use case.最后,关于分布式事务的一句话:我个人建议尽可能不要使用它们,尤其是对于您当前的用例。 They are complex beasts which almost surely impact the availability of your application and increase end-to-end latency of all the processes which are involved in the transaction.它们是复杂的野兽,几乎肯定会影响应用程序的可用性并增加事务中涉及的所有进程的端到端延迟。 Something like the Saga pattern is usually a far better fit for a distributed system.Saga 模式这样的东西通常更适合分布式系统。

Of course, this might not be applicable to your use case and your consistency requirements might outweigh any availability requirements, so take it with a grain of salt.当然,这可能不适用于您的用例,并且您的一致性要求可能超过任何可用性要求,因此请谨慎对待。

Your use case is simple and common.您的用例简单而常见。 You wish to send a JMS message, wait for that to complete, then commit to the database.您希望发送一条 JMS 消息,等待该消息完成,然后提交到数据库。 This is done on two transactions - one for the JMS message and the other for the database.这是在两个事务上完成的——一个用于 JMS 消息,另一个用于数据库。 These transactions both exist in a single transaction context.这些事务都存在于单个事务上下文中。 When you start the JMS transaction, no transaction context will exist, so one will be created.当您启动 JMS 事务时,将不存在事务上下文,因此将创建一个。 When you start the database transaction, it will join the existing transaction context.当您启动数据库事务时,它将加入现有的事务上下文。 These transaction will be synchronized in that the JMS transaction must successfully complete before the database transaction is committed.这些事务将被同步,因为 JMS 事务必须在数据库事务提交之前成功完成。

Central to this operation is the transaction manager.此操作的核心是事务管理器。 Looking at the article that you linked, they make many references to the PlatformTransactionManager .查看您链接的文章,他们对PlatformTransactionManager做了很多引用。 In your use case, the PlatformTransactionManager must be a JTA capable transaction manager.在您的用例中, PlatformTransactionManager必须是支持 JTA 的事务管理器。 A JTA transaction manager will be able to create the transaction context and register and synchronize the transactions. JTA 事务管理器将能够创建事务上下文并注册和同步事务。

Note that these are two local transactions, this is not in any way XA or distributed transactions.请注意,这是两个本地事务,这绝不是 XA 或分布式事务。 In this scenario, if the JMS local transaction fails, then the database local transaction will be rolled back.在这种情况下,如果 JMS 本地事务失败,那么数据库本地事务将被回滚。 More specifically, it is the transaction context that gets marked as rollback only.更具体地说,只有事务上下文被标记为回滚。 If any unhandled exception occurs, then the transaction context is marked rollback only.如果发生任何未处理的异常,则事务上下文仅标记为回滚。 Any attempts to call commit() on the local transactions will fail with a message stating that the transaction context is rollback only.对本地事务调用commit()的任何尝试都将失败,并显示一条消息,指出事务上下文仅回滚。

Achieving this is platform dependent.实现这一点取决于平台。 For example, if your Spring project is deployed in a WAR file on an application server such as JBoss, then the PlatformTransactionManager will be autowired automatically.例如,如果您的 Spring 项目部署在 JBoss 等应用服务器上的 WAR 文件中,则PlatformTransactionManager将自动自动装配。 If you are using Spring Boot, then most configurations do not even include a transaction manager.如果您使用的是 Spring Boot,那么大多数配置甚至不包括事务管理器。

I have a transactional Spring JMS and Camel for Spring Boot here .我有一个事务性 Spring JMS 和 Camel 用于 Spring 在此处启动。 This is a simple message bridge for IBM MQ.这是一个用于 IBM MQ 的简单消息桥。 If nothing else, the Spring JMS and annotations and the example for transactional IBM MQ should be useful.如果不出意外,Spring JMS 和注释以及事务性 IBM MQ 的示例应该很有用。 Maybe the Camel bits are useful as well.也许骆驼钻头也很有用。

Note that the pom.xml file contains:注意pom.xml文件包含:

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

This Spring Boot starter will install and configure the Arjuna JTA transaction manager as the PlatformTransactionManager .这个 Spring 引导启动器将安装和配置 Arjuna JTA 事务管理器作为PlatformTransactionManager

In my example, I have:在我的示例中,我有:

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

This provides very nice logging for the Arjuna JTA transaction manager.这为 Arjuna JTA 事务管理器提供了非常好的日志记录。

Bottom line, get a JTA transaction manager configured as the PlatformTransactionManager .最重要的是,获取一个配置为PlatformTransactionManager的 JTA 事务管理器。 Use this to create a transaction context, and have the two local synchronized transactions in that context.使用它来创建事务上下文,并在该上下文中拥有两个本地同步事务。

The example project should be easy to get to run.示例项目应该很容易运行。 The logging is very informative.日志信息非常丰富。

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

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