前几天,部门一个同事来找我,说他遇到了一个很奇怪的事务问题,代码明明加了 @Transactional
,但是操作数据库的时候却没有回滚,数据依然写进去了。
他已经 debug 一下午了都没解决,我接手后一看,果然,这又是一个Spring Boot 多数据源事务配置引发的“老毛病”……
这篇文章就来记录一下这个排查过程,也希望对同样遇到问题的你有帮助。
🧩 问题描述
同事的代码大致如下:
@Service
public class OrderService {
@Autowired
private OrderMapper orderMapper;
@Transactional
public void createOrder() {
orderMapper.insert(...); // 插入订单
int x = 1 / 0; // 模拟异常
}
}
他使用的是 MyBatis + Spring Boot 多数据源 架构,订单相关的表是放在 secondary 数据源 里。
他期望的是:插入订单后抛出异常,整个事务回滚,订单表中不应该插入任何数据。
但实际却是:
插入成功了!异常也抛出了,事务却没有回滚。
🧐 初步排查
我第一时间排查了以下几个常见的 @Transactional
失效原因:
✅ 是否是自身调用?(不是)
✅ 有没有用异步线程?(没有)
✅ 有没有 try-catch 把异常吞掉?(也没有)
✅ 方法是否是 public?(是)
都排除了!
但问题依然存在,感觉哪里不对劲,于是我问了关键一句:
“你这个方法访问的是哪个数据源?”
💥 真相:Spring Boot 多数据源 + 默认事务管理器
原来他用了两个数据源:
主数据源(primary):系统表
业务数据源(secondary):订单、用户等业务数据
而 Spring Boot 默认只会为主数据源注册事务管理器 PlatformTransactionManager
,也就是说:
你没指定事务管理器的时候,
@Transactional
默认用的是 主数据源事务管理器!
也就是说,同事操作的是 secondary
数据源,但事务却由 primary
的事务管理器在“管理”——当然不会生效!
✅ 正确姿势:指定事务管理器
在多数据源场景下,应该这样写:
@Transactional(transactionManager = "secondaryTransactionManager")
public void createOrder() {
orderMapper.insert(...);
int x = 1 / 0;
}
这样才能确保事务管理器和数据源一一对应,事务才会生效!
🛠 配置方式回顾(以 MyBatis 为例)
@Configuration
public class DataSourceConfig {
@Bean(name = "primaryDataSource")
@Primary
@ConfigurationProperties("spring.datasource.primary")
public DataSource primaryDataSource() {
return DataSourceBuilder.create().build();
}
@Bean(name = "secondaryDataSource")
@ConfigurationProperties("spring.datasource.secondary")
public DataSource secondaryDataSource() {
return DataSourceBuilder.create().build();
}
@Bean(name = "primaryTransactionManager")
public PlatformTransactionManager primaryTransactionManager(
@Qualifier("primaryDataSource") DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
@Bean(name = "secondaryTransactionManager")
public PlatformTransactionManager secondaryTransactionManager(
@Qualifier("secondaryDataSource") DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
}
配置好之后,每个业务模块只要用 @Transactional(transactionManager = "xxx")
就能开启对应数据源的事务。
⚠️ 补充提醒
多数据源下,每个事务管理器是独立的。
@Transactional
默认只绑定主数据源事务管理器,千万别忘了!如果你要跨多个数据源控制事务,请考虑使用:
分布式事务框架(如 Seata)
JTA 事务协调器(如 Atomikos、Bitronix)
✍️ 总结一下
这次帮同事排查的问题虽然表面上看只是 @Transactional
没生效,但其实背后隐藏着 Spring 多数据源的事务管理机制。
一句话总结:
Spring Boot 多数据源下,必须为
@Transactional
指定正确的事务管理器,否则事务不生效!
📌 彩蛋:事务管理器名字怎么知道?
如果你用了 Spring Boot 配置多个 DataSource
,你需要显式为每个 TransactionManager
命名,然后在 @Transactional
注解中使用相同的名字。
举个例子:
@Bean(name = "fooTransactionManager")
public PlatformTransactionManager fooTx(@Qualifier("fooDataSource") DataSource ds) {
return new DataSourceTransactionManager(ds);
}
然后:
@Transactional(transactionManager = "fooTransactionManager")
📣 最后
欢迎大家在评论区分享你们遇到的事务问题,希望这篇文章能帮你少踩一点坑。