You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Spring中创建@Transaction控制双Oracle数据库事务回滚?

Spring Boot多Oracle数据库事务一致性实现

现有数据源配置

已完成第一个Oracle数据源(UniversContract)的配置,代码如下:

@Configuration
@EnableTransactionManagement
@EnableJpaRepositories(basePackages = "br.com.xyz.univers.ms.universcontractsync.repository.universcontract",
        entityManagerFactoryRef = "universContractEntityManagerFactory",
        transactionManagerRef= "universContractTransactionManager"
)
public class UniversContractDatasourceConfiguration {

    @Bean
    @ConfigurationProperties("app.datasource.universcontract")
    public DataSourceProperties universContractDataSourceProperties() {
        return new DataSourceProperties();
    }
    @Bean
    @ConfigurationProperties("app.datasource.universcontract.hikari")
    public DataSource universContractDataSource() {
        return universContractDataSourceProperties().initializeDataSourceBuilder()
                .type(HikariDataSource.class).build();
    }

    @Bean(name = "universContractEntityManagerFactory")
    public LocalContainerEntityManagerFactoryBean universContractEntityManagerFactory(
            EntityManagerFactoryBuilder builder) {
        return builder
                .dataSource(universContractDataSource())
                .packages("br.com.xyz.univers.ms.universcontractsync.models.universcontract")
                .build();
    }

    @Bean(name = "universContractTransactionManager")
    public PlatformTransactionManager universContractTransactionManager(
            final @Qualifier("universContractEntityManagerFactory") LocalContainerEntityManagerFactoryBean universContractEntityManagerFactory) {
        if (universContractEntityManagerFactory == null) {
            throw new SyncUnknowException("universContractEntityManagerFactory is null");
        }
        EntityManagerFactory entityManagerFactory = universContractEntityManagerFactory.getObject();
        if (entityManagerFactory == null) {
            throw new SyncUnknowException("entityManagerFactory is null");
        }
        return new JpaTransactionManager(entityManagerFactory);
    }
}

核心问题

需要实现:当业务方法同时操作两个Oracle数据库时,保证所有数据库操作要么全部成功,要么全部回滚。示例业务方法如下:

@Override
@Transactional("chainedTransactionManager")
public void save(CompanyDTO companyDTO) {
    log.info("method=save  ' - CompanyServiceImpl");
    CompanyDTO companyDTOWithLegacyCode = companyDTO.toBuilder().build();
    legacyCodeService.changeToLegacyCode(companyDTOWithLegacyCode); // 操作第二个数据库
    Company company = companyBuilder.universContractDtoToSisbfEntity(companyDTOWithLegacyCode);
    companyRepository.save(company); // 操作第一个数据库
    legacyCodeService.updateLegacyCode(ClassTypeEnum.COMPANY, companyDTO.getCompanyCode(), company.getCompanyCode()); // 操作第二个数据库
    log.info("finish method=save  ' - CompanyServiceImpl");
}

解决方案

要实现上述需求,需完成以下三步:

1. 配置第二个数据源的完整上下文

参照第一个数据源的配置,创建第二个数据源(比如LegacyCode)的配置类,确保Repository、EntityManagerFactory、TransactionManager都独立配置:

@Configuration
@EnableTransactionManagement
@EnableJpaRepositories(basePackages = "br.com.xyz.univers.ms.universcontractsync.repository.legacycode",
        entityManagerFactoryRef = "legacyCodeEntityManagerFactory",
        transactionManagerRef= "legacyCodeTransactionManager"
)
public class LegacyCodeDatasourceConfiguration {

    @Bean
    @ConfigurationProperties("app.datasource.legacycode")
    public DataSourceProperties legacyCodeDataSourceProperties() {
        return new DataSourceProperties();
    }

    @Bean
    @ConfigurationProperties("app.datasource.legacycode.hikari")
    public DataSource legacyCodeDataSource() {
        return legacyCodeDataSourceProperties().initializeDataSourceBuilder()
                .type(HikariDataSource.class).build();
    }

    @Bean(name = "legacyCodeEntityManagerFactory")
    public LocalContainerEntityManagerFactoryBean legacyCodeEntityManagerFactory(
            EntityManagerFactoryBuilder builder) {
        return builder
                .dataSource(legacyCodeDataSource())
                .packages("br.com.xyz.univers.ms.universcontractsync.models.legacycode")
                .build();
    }

    @Bean(name = "legacyCodeTransactionManager")
    public PlatformTransactionManager legacyCodeTransactionManager(
            final @Qualifier("legacyCodeEntityManagerFactory") LocalContainerEntityManagerFactoryBean legacyCodeEntityManagerFactory) {
        if (legacyCodeEntityManagerFactory == null) {
            throw new SyncUnknowException("legacyCodeEntityManagerFactory is null");
        }
        EntityManagerFactory entityManagerFactory = legacyCodeEntityManagerFactory.getObject();
        if (entityManagerFactory == null) {
            throw new SyncUnknowException("entityManagerFactory is null");
        }
        return new JpaTransactionManager(entityManagerFactory);
    }
}

2. 配置链式事务管理器(ChainedTransactionManager)

创建一个全局的事务管理器,将两个数据源的事务管理器串联起来,实现多数据源的事务控制:

@Configuration
public class ChainedTransactionConfig {

    @Bean(name = "chainedTransactionManager")
    public PlatformTransactionManager chainedTransactionManager(
            @Qualifier("universContractTransactionManager") PlatformTransactionManager universContractTxManager,
            @Qualifier("legacyCodeTransactionManager") PlatformTransactionManager legacyCodeTxManager) {
        return new ChainedTransactionManager(universContractTxManager, legacyCodeTxManager);
    }
}

注意:ChainedTransactionManager的执行顺序是按传入的顺序,回滚时会反向逐个回滚。但它属于弱一致性事务——如果最后一个事务提交失败,前面已经提交的事务无法回滚(因为数据库已经持久化)。如果需要强一致性,需使用XA事务(JTA),但XA会带来性能损耗。

3. 确保业务方法的事务配置正确

在业务方法上通过@Transactional("chainedTransactionManager")指定使用链式事务管理器,同时确保被调用的子方法(比如legacyCodeService中的方法)事务传播属性为默认的REQUIRED(即加入当前事务),无需额外修改。

关键注意事项

  • 两个数据源的配置必须完全独立,包括Repository扫描路径、EntityManagerFactory、TransactionManager的命名
  • 如果需要强一致性事务,需改用XA事务:配置支持XA的数据源(比如OracleXADataSource),并使用JtaTransactionManager
  • 链式事务管理器适合对一致性要求不是极端严格、且希望避免XA性能开销的场景

内容的提问来源于stack exchange,提问作者Sérgio M.

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.05 15:30:44