使用Spring @Transactional跨多个数据源进行事务

41

作为一项交易的一部分,我必须更新两个数据源。

  1. 我在DB1中进行更新。
  2. 然后,我在DB2中进行另一个更新。

如果DB2中的更新失败,我希望回滚DB1和DB2。是否可以使用 @Transactional 实现此操作?

以下是示例代码 -

@Transactional(value="db01TransactionManager")
public void updateDb01() {
    Entity01 entity01 = repository01.findOne(1234);
    entity01.setName("Name");
    repository01.save(entity01);

    //Calling method to update DB02
    updateDb02();
}

@Transactional(value="db02TransactionManager")
public void updateDb02() {
    Entity02 entity02 = repository02.findOne(1234);
    entity02.setName("Name");
    repository02.save(entity02);

    //Added this to force a roll back for testing
    TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
}

我的问题是,updateDb02中的setRollbackOnly()只回滚了Db01的事务。


你需要一个支持XA的单一事务管理器。 - Tobb
4个回答

54

我使用ChainedTransactionManager解决了这个问题 - http://docs.spring.io/spring-data/commons/docs/1.6.2.RELEASE/api/org/springframework/data/transaction/ChainedTransactionManager.html

Spring Boot配置:

    @Bean(name = "chainedTransactionManager")
    public ChainedTransactionManager transactionManager(@Qualifier("primaryDs") PlatformTransactionManager ds1,
                                                    @Qualifier("secondaryDs") PlatformTransactionManager ds2) {
         return new ChainedTransactionManager(ds1, ds2);
    }

然后您可以按以下方式使用它:

@Transactional(value="chainedTransactionManager")
public void updateDb01() {
    Entity01 entity01 = repository01.findOne(1234);
    entity01.setName("Name");
    repository01.save(entity01);

    //Calling method to update DB02
    updateDb02();
}

public void updateDb02() {
    Entity02 entity02 = repository02.findOne(1234);
    entity02.setName("Name");
    repository02.save(entity02);

    //Added this to force a roll back for testing
    TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
}

1
只有 ChainedTransactionManager 对我有用。我必须处理 4 个数据库,这是唯一能帮助我的东西。 - Logic
6
对于我未来的自己和其他人:如果你想让@Transactional默认使用ChainedTransactionManager,请在ChainedTransactionManager bean上添加@Primary - user2652379
正是我的问题,恰好适用的解决方案...谢谢! - TooLiPHoNe.NeT
3
这里是我写的一篇文章,演示如何设置一个链接式事务管理器,并提供更为详细的代码示例。这可能有助于解释所有组件如何协同工作,以产生这个结果:https://metamorphant.de/blog/posts/2021-03-21-distributed-transactions-across-multiple-dbs-chainedtransactionmanager/ - dribnif
11
请注意,由于 ChainedTransactionManager 的行为不符合预期,它已被弃用。有关更多信息,请参阅此ticket - Mo'ath Alshorman

3
最好的方法是创建一个第三个方法,并将其注释为@Transactional
@Transactional(readOnly = false)
public void updateCommon(){
  upbateDb01();
  upbateDb02();
}

根据Spring文档,当第一个注释出现时,事务控制就开始了,因此在这种情况下,当调用updateCommon时将启动单个事务。 更新 但是,如果您使用CrudRepository或类似的内容,则会起作用。
在多个数据源的情况下,您可以尝试使用全局事务管理概念。以下是Spring文档中的示例:
@Inject private PlatformTransactionManager txManager; 

TransactionTemplate template  = new TransactionTemplate(this.txManager); 
template.execute( new TransactionCallback<Object>(){ 
  public void doInTransaction(TransactionStatus status){ 
   // work done here will be wrapped by a transaction and committed. 
   // the transaction will be rolled back if 
   // status.setRollbackOnly(true) is called or an exception is thrown 
  } 
});

这里有一个链接:http://spring.io/blog/2011/08/15/configuring-spring-and-jta-without-full-java-ee/ 我自己从未使用过它,所以并没有深入探究这个主题。 希望它能对你有所帮助。


1
我的问题是因为我有多个数据源,它将使用此 @Transactional 启动一个 transactionManager,只有该 transactionManager 可以参与事务。因此,使用您的解决方案,我仍然会遇到一个问题,即只有一个 transactionManager 适用于我所有的数据库更新。 - Do Will
该死,你说得对,我不知怎么错过了它(我会根据正确的条件更新我的答案)。 - Yuriy Tsarkov

1

问题解决了。

这些方法必须在不同的bean中才能使用不同的事务管理器。


1
如果它们在不同的Beans中,我想您就不会遇到这个问题本身了。 - DKG

0

我相信你已经像下面这样定义了你的交易。

@Bean(name="db01TransactionManager") 
@Autowired
DataSourceTransactionManager tm1(@Qualifier ("datasource1") DataSource datasource) {
    DataSourceTransactionManager txm  = new DataSourceTransactionManager(datasource);
    return txm;
}

@Bean(name="db02TransactionManager") 
@Autowired
DataSourceTransactionManager tm2(@Qualifier ("datasource2") DataSource datasource) {
    DataSourceTransactionManager txm  = new DataSourceTransactionManager(datasource);
    return txm;
}

现在最简单的方法是尝试、捕获并回滚两个事务。但如果您仍然想委托,可以选择以下选项。

创建自己的并覆盖回滚方法并使用它。

@Bean(name=“allTransactionManager") 
@Autowired
DataSourceTransactionManager tm2(@Qualifier ("datasource1”) DataSource datasource1, @Qualifier ("datasource2") DataSource datasource2) {

    DataSourceTransactionManager txm  = new MyDataSourceTransactionManager(datasource1,datasouce2);
        return txm;
}

并定义您自己的事务管理器为。

MyDataSourceTransactionManager extends DataSourceTransactionManager{
DataSourceTransactionManager tm1; 
DataSourceTransactionManager tm2; 

MyDataSourceTransactionManager(Datasource ds1,Datasource d2){
  tm1 = new DataSourceTransactionManager(DataSource);
  tm2 =new DataSourceTransactionManager(DataSource);
}
// override and for roll back, rollback for both of tm1 and tm2. Thus all actions are delegated in this class

}

然后在需要同步工作的 DAO 层中使用它。

 @Transactional("allTransactionManager")

现在我们有了自己的事务管理器,可以对两种类型的事务进行回滚或提交。


看起来不错。TS @Do Will,请告诉我们您是否会尝试这个并给我们反馈。 - Yuriy Tsarkov

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接