网站Logo Ilren 小记

SpringBoot多数据源场景下,@Transactional 为什么不生效?实战排查记录

jack
9
2023-06-14

前几天,部门一个同事来找我,说他遇到了一个很奇怪的事务问题,代码明明加了 @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")

📣 最后

欢迎大家在评论区分享你们遇到的事务问题,希望这篇文章能帮你少踩一点坑。

动物装饰