Spring 事务失效的 8 种场景,很多老手依然频繁踩雷

Spring 事务失效的 8 种场景,很多老手依然频繁踩雷

Spring 事务,是面试高频,也是线上事故的"重灾区"。

很多同学觉得:

加个 @Transactional 就万事大吉了。

但在真实项目中,事务不回滚、部分回滚、甚至根本没开启,每天都在发生。

本文盘点 Spring 事务失效的 8 大经典场景,从原理 → 反例 → 正解 → 源码级原因,一次性讲透。


先给结论(速记版)

Spring 事务的本质是 AOP + 代理

只要代理没生效,或异常被"吃掉",事务一定会翻车


一、方法不是 public:最基础却最高频

❌ 错误示例

typescript 复制代码
@Transactional
protected void createOrder() {
    orderMapper.insert(order);
    throw new RuntimeException();
}

✅ 原因

Spring 的事务 AOP 默认使用 JDK 动态代理 / CGLIB

  • JDK 动态代理 → 只能代理接口方法
  • CGLIB → 只能拦截 public 方法

@Transactional 注解在 protected / private / package 方法上:

不会被代理,事务直接失效

✅ 正确写法

typescript 复制代码
@Transactional
public void createOrder() {
    ...
}

📌 经验法则

事务方法一律写成 public


二、自调用(this 调用):老手最容易栽的坑

❌ 错误示例

typescript 复制代码
@Service
public class OrderService {

    public void create() {
        save();
    }

    @Transactional
    public void save() {
        orderMapper.insert(order);
        throw new RuntimeException();
    }
}

✅ 原因

create() 内部调用 save()

kotlin 复制代码
this.save(); // 调用的是目标对象本身

而 Spring 事务是通过 代理对象​ 实现的:

kotlin 复制代码
外部调用 → 代理 → 目标方法
this调用 → 目标方法(绕过代理)

👉 事务完全失效

✅ 解决方案

方案 1:拆分 Service(推荐)
less 复制代码
@Service
public class OrderTxService {
    @Transactional
    public void save() { ... }
}

@Service
public class OrderService {
    @Autowired
    private OrderTxService txService;

    public void create() {
        txService.save();
    }
}
方案 2:注入自己(不优雅但可用)
ruby 复制代码
@Autowired
private OrderService self;

self.save();
方案 3:AspectJ 模式(高级)
ini 复制代码
spring.aop.proxy-target-class=true

三、异常类型不对:RuntimeException 与 Exception 的坑

❌ 错误示例

java 复制代码
@Transactional
public void create() throws Exception {
    orderMapper.insert(order);
    throw new Exception();
}

✅ 原因

@Transactional 默认规则:

异常类型 是否回滚
RuntimeException & Error
Checked Exception

Exception 属于 受检异常,默认不回滚。

✅ 正确写法

java 复制代码
@Transactional(rollbackFor = Exception.class)
public void create() throws Exception {
    ...
}

📌 强烈建议:

永远显式声明 rollbackFor

python 复制代码
@Transactional(rollbackFor = Exception.class)

四、异常被吞掉:最隐蔽的"假成功"

❌ 错误示例

csharp 复制代码
@Transactional(rollbackFor = Exception.class)
public void create() {
    try {
        orderMapper.insert(order);
        int i = 1 / 0;
    } catch (Exception e) {
        log.error("error", e);
        // ⚠️ 吞掉异常
    }
}

✅ 原因

Spring 事务回滚的前提是:

异常抛出到代理层

你在方法内部把异常吃了,代理完全不知道发生了异常。

✅ 正确写法

php 复制代码
catch (Exception e) {
    log.error("error", e);
    throw e; // 或 new RuntimeException(e)
}

📌 记住一句话:

事务回滚靠"抛异常",不是靠"打日志"


五、传播行为配置错误:REQUIRES_NEW 的误用

❌ 错误示例

java 复制代码
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void log() {
    logMapper.insert(log);
}

外层事务回滚,日志却提交了。

✅ 原因

REQUIRES_NEW 的含义:

挂起外层事务,新建一个独立事务

  • 内外事务完全隔离
  • 外层回滚不影响内层

✅ 正确使用场景

✅ 审计日志

✅ 消息发送

✅ 不希望被主事务影响的操作

❌ 错误使用场景

❌ 核心业务数据

❌ 需要一起回滚的操作

📌 90% 的业务场景只需要:

ini 复制代码
@Transactional(propagation = Propagation.REQUIRED)

六、数据库引擎不支持事务

❌ 典型场景

MySQL 使用 MyISAM

ini 复制代码
CREATE TABLE order (
  ...
) ENGINE=MyISAM;

✅ 原因

  • MyISAM:不支持事务
  • InnoDB:支持事务

Spring 再怎么努力也没用。

✅ 检查方式

ini 复制代码
SHOW TABLE STATUS WHERE Name = 'order';

✅ 正确做法

ini 复制代码
ENGINE=InnoDB;

📌 现在还有人用 MyISAM,基本可以判定为事故级问题


七、非事务方法调用事务方法 + 多线程

❌ 错误示例

scss 复制代码
@Transactional
public void create() {
    new Thread(() -> {
        orderMapper.insert(order);
    }).start();
}

✅ 原因

Spring 事务是基于 ThreadLocal​ 绑定的:

复制代码
一个线程 = 一个数据库连接 = 一个事务

新线程:

  • 没有事务上下文
  • 没有数据库连接绑定

👉 子线程操作不在事务中

✅ 正确做法

  • 不要在事务方法中开线程
  • 使用异步 + 新事务
less 复制代码
@Async
@Transactional
public void asyncCreate() { ... }

八、没有被 Spring 管理:忘了 @Service / @Component

❌ 错误示例

typescript 复制代码
// @Service 忘了写
public class OrderService {

    @Transactional
    public void create() {
        orderMapper.insert(order);
    }
}

✅ 原因

Spring 只对 Bean​ 创建代理。

不是 Bean → 没有代理 → 没有事务。

✅ 正确写法

kotlin 复制代码
@Service
public class OrderService { ... }

📌 排查技巧:

  • 看启动日志有没有 Bean 注册
  • Debug 看 this 是不是代理对象

九、Spring 事务失效全景对照表(建议收藏)

场景 是否失效 原因
非 public 方法 AOP 限制
this 自调用 绕过代理
吞掉异常 未抛到代理
非 RuntimeException rollbackFor
REQUIRES_NEW 误用 ⚠️ 设计问题
MyISAM 引擎不支持
多线程 ThreadLocal
非 Spring Bean 无代理

十、如何快速定位事务问题?

✅ 1️⃣ 打印代理对象

less 复制代码
log.info("class={}", AopContext.currentProxy().getClass());

✅ 2️⃣ 开启事务日志

ini 复制代码
logging.level.org.springframework.jdbc.datasource.DataSourceTransactionManager=DEBUG

✅ 3️⃣ Debug 断点

  • TransactionInterceptor
  • DataSourceTransactionManager

十一、一句话总结

Spring 事务不是魔法,而是代理。

  • 代理没生效 → 事务失效
  • 异常没抛出 → 不会回滚
  • 多线程 / 自调用 → 上下文丢失

真正理解这三点,事务问题至少少踩 80% 的坑。

相关推荐
六边形66613 小时前
独立开发不知道做什么?使用 TRAE Work 抓取差评痛点,快速跑通产品立项流
前端·后端·面试
高频因子挖掘机13 小时前
量化交易系统的数据层和策略层如何解耦?从紧耦合泥潭到优雅分层架构
后端·github
临江仙45513 小时前
同一个 AI Agent 如何同时服务 Web、微信和 QQ:PureChat 的渠道架构实践
前端·人工智能·后端
我的AI队友13 小时前
DeepSeek Harness 接钉钉通知,踩了两个坑:签名不匹配 + 纯对话刷屏
后端·deepseek
程序员鱼皮13 小时前
16 个超火的 DeepSeek Harness 插件,大肥鱼已经落后 N 个版本了。。。
前端·后端·ai编程
步行cgn13 小时前
Spring Cache 详解:Spring 框架的缓存抽象
后端
eralong13 小时前
Java IO 与 NIO
java·后端
量化小c13 小时前
从数据到策略:QuantDash + DuckDB 搭建 5 分钟 K 线本地量化数据仓库
后端·github
马可家的菠萝13 小时前
AI Agent Demo 10 分钟就能跑,真正难的是让它 200 个场景都别乱来
前端·后端·aigc