首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >@Transactional 到底要不要加 rollbackFor?

@Transactional 到底要不要加 rollbackFor?

原创
作者头像
程序搭建开发i
发布2026-07-02 10:02:15
发布2026-07-02 10:02:15
250
举报

@Transactional 到底要不要加 rollbackFor?我建议你先看完这个事故

很多团队都有一个习惯:所有 @Transactional 后面都顺手加上 rollbackFor = Exception.class。 也有人反对:不要无脑加,Spring 默认只回滚运行时异常是有原因的。 那到底该不该加?

一、事故现场

线上有一个订单确认接口,逻辑很简单:

  1. 更新订单状态为 CONFIRMED
  2. 扣减库存
  3. 写操作日志

这三个动作必须在同一个事务里。

代码大概长这样:

按理说,库存扣减失败,整个事务应该回滚。

但数据库里订单状态已经提交了。

第一反应通常是:

@Transactional 又失效了?

其实不是注解没生效,而是异常类型不对。


二、问题藏在 throws Exception 里

看一下 stockService.deduct(orderId) 的实现:

注意这里抛的是:

这是一个受检异常,也就是 checked exception。

而 Spring 事务默认只会回滚这两类异常:

默认不会因为 checked exception 回滚。

所以当 deduct() 抛出 Exception 时,外层方法确实中断了,调用方也确实看到了异常。

但是 Spring 事务拦截器判断后发现:

这是 checked exception,不在默认回滚范围内。

于是事务提交了。

订单状态就这样被提交到了数据库。


三、最小复现

下面这段代码就能复现这个问题。

调用:

你会发现:

这就是最容易误判的地方。

很多人以为:

只要方法抛异常,事务就一定回滚。

实际上不是。

更准确地说:

只有抛出的异常命中了事务回滚规则,Spring 才会回滚。


四、为什么 Spring 默认不回滚 checked exception?

这不是 Spring 的 bug。

Spring 默认遵循的是 EJB 时代留下来的事务语义:

比如这些异常从语义上看,可能只是业务流程的一部分:

Spring 没法判断你的 checked exception 到底意味着什么。

它不知道“库存不足”是应该回滚,还是应该允许前面的操作提交。

所以它选择了一个保守默认值:

checked exception 不回滚,除非你明确告诉 Spring 要回滚。


五、修复方式 1:加 rollbackFor

最直接的修复方式:

加上后,只要方法抛出 Exception 或它的子类,事务都会回滚。

也就是说:

代码语言:javascript
复制
throw new Exception("库存不足");

现在会触发回滚。

很多团队会把它当成默认写法:

代码语言:javascript
复制
@Transactional(rollbackFor = Exception.class)

这个习惯不是没有道理。

因为在真实业务代码里,很多人会随手写:

代码语言:javascript
复制
throws Exception

或者在底层工具、RPC、文件、消息、第三方接口里抛出 checked exception。

如果事务方法里没加 rollbackFor,就很容易出现:

异常抛出去了,但数据已经提交了。


六、修复方式 2:抛 RuntimeException

另一种方式是把业务异常设计成运行时异常。

然后业务里这样写:

因为 BizException 继承了 RuntimeException,所以即使不写 rollbackFor,事务也会默认回滚。

这也是很多项目里的统一异常设计:

这样事务回滚规则会简单很多。


七、那到底该不该所有事务都加 rollbackFor?

我的建议是:

业务系统里,绝大多数写操作事务,都建议加 rollbackFor = Exception.class

尤其是这些场景:

场景

建议

订单、支付、库存、账户余额

必须加

多表写入,要求强一致

必须加

调用底层组件可能抛 checked exception

建议加

历史代码里大量 throws Exception

建议加

只读查询事务

意义不大

明确希望 checked exception 不回滚

不要加,单独声明规则

为什么我倾向于加?

因为线上事故里,“该回滚却没回滚”的代价,通常比“多回滚了一次”的代价更高。

订单状态、账户金额、库存数量这种数据,一旦错了,后面补偿很麻烦。

所以我更愿意让事务默认更谨慎一点。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • @Transactional 到底要不要加 rollbackFor?我建议你先看完这个事故
    • 一、事故现场
    • 二、问题藏在 throws Exception 里
    • 三、最小复现
    • 四、为什么 Spring 默认不回滚 checked exception?
    • 五、修复方式 1:加 rollbackFor
    • 六、修复方式 2:抛 RuntimeException
    • 七、那到底该不该所有事务都加 rollbackFor?
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档