Transactional

xiaopeng
发布于 2026-08-18 / 8 阅读
0
0

Transactional

为什么 @Transactional 会失效? ⭐⭐⭐⭐⭐

面试频率:★★★★★

工作频率:★★★★★


⚡30 秒速记(复习只看这里)

🟢 一句话

Spring 事务本质依赖 Spring AOP Proxy,只有经过代理对象调用,事务才会生效。


🟡 五大高频原因(★★★★★)

  1. 同类内部调用(this.xxx())
  2. private 方法
  3. final 方法
  4. 异常被 catch
  5. rollbackFor 未配置

🟠 REQUIRES_NEW

  • 挂起外层事务
  • 开启独立事务
  • 内外事务互不影响
  • 常用于日志、审计、失败记录

🔴 一句话

事务本质是 AOP Proxy,不经过代理对象,事务不会生效。


📚 完整知识

一、事务底层原理

Spring 并不是因为 @Transactional 注解才有事务。

真正执行流程:

Controller
    │
    ▼
Spring Proxy(代理对象)
    │
    ▼
TransactionInterceptor
    │
    ▼
PlatformTransactionManager
    │
    ▼
开启事务
    │
    ▼
执行目标方法
    │
    ├── 成功 → Commit
    └── 异常 → Rollback

真正负责事务的是:

TransactionInterceptor

不是注解。


二、为什么会失效?

① 同类内部调用(★★★★★)

错误

@Service
public class OrderService {

    public void createOrder() {
        saveOrder();
    }

    @Transactional
    public void saveOrder() {

    }

}

实际上执行的是:

this.saveOrder();

没有经过 Spring Proxy。

事务不会开启。


正确

@Service
public class OrderService {

    @Autowired
    private OrderTxService orderTxService;

    public void createOrder() {
        orderTxService.saveOrder();
    }

}

② private 方法(★★★★★)

@Transactional
private void save() {

}

Spring 默认不会代理 private 方法。

事务失效。


③ final 方法(★★★★☆)

@Transactional
public final void save() {

}

CGLIB 需要重写方法。

final 无法重写。

事务失效。


④ 异常被 catch(★★★★★)

错误:

@Transactional
public void save() {

    try {

        int i = 1 / 0;

    } catch (Exception e) {

        log.error("error");

    }

}

Spring认为:

方法正常结束。

最终:

Commit

正确:

catch (Exception e) {

    throw e;

}

或者:

TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();

⑤ rollbackFor 未配置(★★★★★)

默认只回滚:

RuntimeException

Error

例如:

throw new Exception();

不会回滚。

正确:

@Transactional(
    rollbackFor = Exception.class
)

三、REQUIRES_NEW

@Transactional(
    propagation = Propagation.REQUIRES_NEW
)

表示:

挂起外层事务,开启新的独立事务。

例如:

外层事务

↓

保存订单

↓

调用 saveLog()

↓

REQUIRES_NEW

↓

日志提交成功

↓

恢复外层事务

↓

订单异常

↓

订单回滚

最终:

订单 ❌

日志 ✅

适用:

  • 操作日志
  • 审计日志
  • 消息记录
  • 失败记录

一般不要用于订单、库存等核心业务。


💼 工作场景

场景一:本地事务

保存订单

↓

扣库存

↓

更新积分

如果:

更新积分失败

最终:

订单回滚

库存回滚

积分回滚

属于:

本地事务


场景二:数据库 + MQ

保存订单

↓

扣库存

↓

发送 MQ

↓

更新积分

如果:

MQ 已发送成功

↓

更新积分失败

↓

数据库回滚

最终可能:

数据库没有订单

MQ 已消费

普通 @Transactional

无法保证:

数据库 + MQ 原子性

通常需要:

  • 本地消息表
  • RocketMQ 事务消息
  • 最终一致性方案

场景三:微服务

订单服务

↓

库存服务

↓

积分服务

即使:

@Transactional

也只能保证:

订单数据库

不能保证:

库存服务

积分服务

一起回滚。

属于:

分布式事务


🎤 面试回答(30 秒)

Spring 的事务底层是通过 AOP 代理实现的,而不是 @Transactional 注解本身。只有方法经过 Spring 创建的代理对象调用时,事务才会生效。如果同类内部调用、private 方法、final 方法、异常被 catch 没有继续抛出,或者抛出的异常不是默认回滚类型,都可能导致事务失效。另外,REQUIRES_NEW 会开启一个新的独立事务,即使外层事务回滚,内层事务也不会回滚,因此一般用于日志、审计等需要独立提交的场景。


🎯 面试追问

Q1:为什么 this 调用会失效?

答:

没有经过 Spring Proxy。

没有执行 TransactionInterceptor。


Q2:为什么事务依赖代理?

答:

Spring 使用 AOP 增强目标方法。

真正开启事务的是:

TransactionInterceptor

Q3:为什么 final 不行?

答:

CGLIB 通过继承重写方法实现增强。

final 无法重写。


Q4:数据库 + MQ 为什么不能只靠 @Transactional?

答:

@Transactional 只能保证本地数据库事务。

MQ 属于另外一种资源。

需要事务消息、本地消息表等方案保证最终一致性。


⚠️ 工作踩坑

✅ this.xxx()

事务失效


✅ catch 后没有继续抛异常

事务提交


✅ checked Exception

没有 rollbackFor

事务提交


✅ 滥用 REQUIRES_NEW

部分数据提交

部分数据回滚


❓ 自测(复习时只看这里)

  1. 为什么 @Transactional 会失效?
  2. Spring 事务底层是谁实现的?
  3. 为什么 this.xxx() 不生效?
  4. rollbackFor 默认回滚哪些异常?
  5. REQUIRES_NEW 是什么?
  6. 数据库 + MQ 为什么属于一致性问题?
  7. 什么情况下属于分布式事务?

评论