关于多数据源下Spring声明式事务管理失效问题的分析与解决

背景

项目中数据持久层采用了mybatis组件,存在多个数据源,通过@MapperScan的方式声明不同包路径下的dao使用哪个数据源,代码示例如下:

java 复制代码
@Configuration
@MapperScan(basePackages = {"com.company.first.**.dao"}, sqlSessionFactoryRef = "firstSqlSessionFactory")
public class FirstDataSourceConfig {
    @Autowired
    private FirstDataSourceProperties dataSourceProperties;

    @Bean(name = "firstDataSource")
    @Override
    public DataSource dataSource() {
        HikariConfig config = getConfig(dataSourceProperties);
        HikariDataSource dataSource = new HikariDataSource(config);
        return dataSource;
    }

    @Bean(name = "firstTransactionManager")
    @Primary
    @Override
    public DataSourceTransactionManager transactionManager() {
        return new DataSourceTransactionManager(dataSource());
    }
    
    @Bean(name = "firstSqlSessionFactory")
    @Primary
    @Override
    public SqlSessionFactory sqlSessionFactory() throws Exception {
        return createSqlSessionFactory(dataSource());
    }
}

其他 SecondDataSourceConfig 代码类似,只是没有 @Primary 注解。

现在有两个服务类A和B,分别有方法1和方法2,方法1调用方法2,方法1的的数据源为1,方法2的数据源为2,示例如下:

java 复制代码
@Service
public class A {
    @Transactional(rollbakFor = Exception.class)
    public void method1() {
        //通过数据源1 操作数据
        dao1.insert(record1);
        
        b.method2();
        
        //其他操作发生错误
        throw new RuntimeException();
    }
}
@Service
public class B {
    @Transactional(rollbakFor = Exception.class)
    public void method2() {
        //通过数据源2 操作数据
        dao2.insert(record2);
    }
}

问题描述:在调用a.method1()时发生了错误,此时数据库中数据1发生了回滚,数据2未发生回滚,这是不合理的,预期的结果是 数据1和2同时回滚。

分析解决

方式一(失败的)

可以看到 方法1和2都通过Spring的注解式声明开启了事务,且这个时候数据源1的操作是有事务管理,数据源2的操作没有事务管理,于是想是不是事务管理器的问题? 这里两个注解声明都没有指定事务管理器,因此这里默认的事务管理器均为 firstTransactionManager。

Spring在没有指定事务管理器的时候会优先找有没有被标注为@Primary的事务管理器,如果没有会找名称为transactionManager的事务管理器,如果都找不到,Spring将无法确定要使用的事务管理器,从而抛出异常。

而方法2的数据源2其实是不被 firstTransactionManager管理的,因此这里方法2是没有事务管理的,于是想通过指定方法2的事务管理器是不是就解决问题了。

java 复制代码
    @Transactional(rollbakFor = Exception.class, transactionManager = "secondTransactionManager")
    public void method2() {
        //通过数据源2 操作数据
        dao2.insert(record2);
    }

经过测试,问题仍然存在。即使通过设置事务传播行为,开启新的事务,问题仍然存在。

java 复制代码
    @Transactional(rollbakFor = Exception.class, transactionManager = "secondTransactionManager" propagation = Propagation.REQUIRES_NEW)
    public void method2() {
        //通过数据源2 操作数据
        dao2.insert(record2);
    }

其实这里因为事务管理器不同,即使方法2的事务传播行为是默认的 Propagation.REQUIRED加入现有事务的类型,因为不同管理器管理的事务是相互独立的,无法直接加入,所以方法2也是在对应的事务管理器下开启新的事务。那么这里为什么方法2的数据2没有跟方法1中的数据1一起回滚?

因为这里方法2是独立的事务2,在方法2调用结束后即结束了事务(提交或回滚),这里方法2本身没有异常,所以事务2提交成功。而事务1在方法2之后发生异常,事务1回滚。其流程如下:

复制代码
方法1调用开始
事务1开启
操作数据1
方法2调用开始
事务2开启
操作数据2
事务2提交
方法2调用结束
其他操作,发生异常
事务1回滚数据1

方式二

通过方式一的分析我们知道需要事务1和事务2一起提交或回滚。那么事务1和事务2能否合并在一个事务内,也就是说方法1和方法2使用同一个事务管理器。

链式事务管理器

java 复制代码
    @Bean(name = "chainedTransactionManager")
    public PlatformTransactionManager transactionManager(
            @Qualifier("firstTransactionManager") DataSourceTransactionManager ds1,
            @Qualifier("secondTransactionManager") DataSourceTransactionManager ds2) {
        return new ChainedTransactionManager(ds1, ds2);
    }

指定事务管理器

java 复制代码
@Service
public class A {
    @Transactional(rollbakFor = Exception.class, transactionManager = "chainedTransactionManager")
    public void method1() {
        //通过数据源1 操作数据
        dao1.insert(record1);
        
        b.method2();
        
        //其他操作发生错误
        throw new RuntimeException();
    }
}

@Service
public class B {
    @Transactional(rollbakFor = Exception.class)
    public void method2() {
        //通过数据源2 操作数据
        dao2.insert(record2);
    }
}

可以看到方法2未指定事务管理器,因为方法2会继承方法1中chainedTransactionManager管理的事务上下文,方法2中操作都会被纳入同一个chainedTransactionManager事务管理。 不过此时因为事务管理器不同,当我指定事务传播行为是 Propagation.NOT_SUPPORTED不起作用,需要指定使用同一个事务管理器。

但是指定 Propagation.NEVER时却会报存在事务的错误,有点奇怪。

其他方案

1 使用JTA/XA分布式事务管理器(推荐用于强一致性场景)

2 使用Seata等分布式事务框架(微服务架构推荐)

3 使用编程式事务管理(精细控制)

4 使用Spring Data的AbstractRoutingDataSource 来管理多个数据源(适合读写分离等场景)

相关推荐
Leon-Ning Liu2 分钟前
【真实经验分享】Oradebug抓取阻塞源SQL语句
数据库
观远数据7 分钟前
消费品牌选型BI的能力边界:三类差异化场景的适配清单与排除项
数据库·人工智能·microsoft
TlSfoward16 分钟前
爬虫指纹漂移监控与回归测试:JA3/JA4 变化为什么会影响线上验证 TLSFOWARD
数据库·爬虫·网络协议·搜索引擎
cmes_love35 分钟前
期货Level2与股票逐笔行情数据有什么,如何下载
数据库·区块链
—Miss. Z—44 分钟前
计算机二级MySQL——基本操作题
数据库·mysql·oracle
eddietao1 小时前
MySQL+SQLServer数据库-笔记
数据库·笔记·mysql
XS0301061 小时前
Spring AI第一课
数据库·人工智能·spring
半夜修仙2 小时前
Spring集成邮箱功能
java·开发语言·spring boot·spring·rabbitmq·github·maven
宠友信息2 小时前
从重复请求到订单同步看内容社区源码的处理思路
java·spring boot·redis·后端·mysql·spring
LaughingZhu2 小时前
Product Hunt 每日热榜 | 2026-07-17
前端·数据库·人工智能·经验分享·mysql·chatgpt·html