qinyelin
发布于 2026-08-18 / 35 阅读
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. 什么情况下属于分布式事务?


Spring 事务 Commit / Rollback 原理 ⭐⭐⭐⭐⭐

面试频率:★★★★☆
核心理解:Spring 负责管理事务,真正的数据提交和回滚由数据库完成。


⚡30 秒速记

@Transactional
      ↓
Spring AOP代理
      ↓
事务拦截器
      ↓
开启数据库事务
      ↓
执行SQL
      ↓
方法正常?
   ↙       ↘
 是         否
 ↓          ↓
commit    判断是否需要rollback

最重要的一句话:

Spring 不会自己把 SQL 反着执行,Spring 负责控制 commit / rollback,真正的数据恢复由数据库事务机制完成。

MySQL InnoDB 中:

undo log
= 回滚 + MVCC历史版本的重要基础

redo log
= 崩溃恢复的重要基础

一、@Transactional 到底做了什么?

例如:

@Transactional
public void createOrder() {

    insertOrder();

    updateStock();

}

看起来:

调用createOrder()

↓

执行SQL

实际上 Spring 还帮我们管理了事务。

大致流程:

调用者
   ↓
Spring代理对象
   ↓
事务拦截器
   ↓
开启事务
   ↓
执行createOrder()
   ↓
insertOrder()
   ↓
updateStock()
   ↓
方法正常结束
   ↓
commit

所以:

@Transactional

本身只是事务配置。

真正处理事务的是:

Spring事务代理
+
事务拦截器
+
事务管理器

二、正常情况下怎么提交?

例如:

@Transactional
public void createOrder() {

    insertOrder();

    updateStock();

}

可以粗略理解:

Spring开启事务
     ↓
获得数据库Connection
     ↓
关闭自动提交
autoCommit = false
     ↓
执行INSERT
     ↓
执行UPDATE
     ↓
方法正常结束
     ↓
commit()

最终:

事务提交

数据库中的修改正式完成。


三、发生异常怎么办? ⭐⭐⭐⭐⭐

例如:

@Transactional
public void createOrder() {

    insertOrder();

    updateStock();

    throw new RuntimeException();

}

执行:

Spring开启事务
     ↓
INSERT
     ↓
UPDATE
     ↓
RuntimeException
     ↓
异常抛给事务拦截器
     ↓
判断是否满足回滚规则
     ↓
满足
     ↓
rollback()

最终:

INSERT

+

UPDATE

↓

一起回滚

四、Spring 是怎么把数据恢复的?

很多人第一次会认为:

INSERT
 ↓
发生异常
 ↓
Spring执行DELETE

或者:

UPDATE 1000 → 500
 ↓
发生异常
 ↓
Spring再执行UPDATE 500 → 1000

不是。

Spring:

不会自己把 SQL 反着执行一遍。

Spring主要负责:

告诉数据库:

这个事务需要rollback

真正恢复数据的是:

数据库

五、Spring 和数据库分别负责什么? ⭐⭐⭐⭐⭐

Spring

负责:

什么时候开启事务

↓

哪些方法加入事务

↓

事务传播

↓

什么时候commit

↓

什么时候rollback

例如:

@Transactional

属于 Spring 的事务管理。


数据库

负责:

真正执行事务

↓

数据修改

↓

事务隔离

↓

锁

↓

commit

↓

rollback

↓

日志 / MVCC 等底层机制

所以可以简单记:

Spring

=

事务管理员


数据库

=

真正执行事务的人

六、MySQL 为什么能 rollback?

如果使用:

MySQL InnoDB

这里就涉及:

undo log

中文:

回滚日志。


例如:

原来:

张三余额 = 1000

执行:

UPDATE account
SET balance = 500
WHERE name = '张三';

事务还没有提交。

InnoDB 会维护用于恢复旧数据的信息。

可以粗略理解:

原数据:

balance = 1000

↓

记录旧版本相关信息

↓

修改数据

↓

balance = 500

如果:

COMMIT

事务正常提交。

如果:

ROLLBACK

数据库可以根据 undo 信息:

500

↓

恢复

↓

1000

七、undo log 是什么? ⭐⭐⭐⭐⭐

undo:

撤销

所以:

undo log

=

撤销日志 / 回滚日志

主要作用之一:

事务回滚

例如:

1000
 ↓
修改
 ↓
500

undo 中保留:

恢复旧数据所需要的信息

发生:

rollback

就可以:

500
 ↓
恢复
 ↓
1000

八、undo log 不只是用来回滚

undo log 还有一个非常重要的作用:

MVCC

你前面事务隔离学过:

REPEATABLE READ
       ↓
MVCC
       ↓
历史版本

undo log:

和这些历史版本密切相关。

可以先简单理解:

             undo log
              /    \
             /      \
        rollback    MVCC
           ↓          ↓
        恢复数据    历史版本

所以:

undo log 既是事务回滚的重要基础,也和 MVCC 的历史版本读取密切相关。


九、举个 MVCC 的简单例子

原来:

张三余额 = 1000

事务B:

修改:

1000 → 500

数据库中:

会维护相关版本信息。

事务A:

根据:

MVCC
+
Read View

可能仍然读取:

1000

其他满足条件的事务:

可能读取:

500

所以:

undo log
     ↓
历史版本
     ↓
MVCC
     ↓
不同事务读取合适版本

注意:

这里先理解概念。

具体:

版本链

trx_id

roll_pointer

Read View

后面学 MySQL MVCC 再详细讲。


十、redo log 又是什么?

除了:

undo log

MySQL InnoDB 还有:

redo log

redo:

重新做

它主要和:

崩溃恢复、持久性

有关。

例如:

事务已经提交。

但是突然:

服务器断电

MySQL 重启以后:

可以利用 redo log 等机制:

恢复已经提交但尚未完整落盘的数据修改

十一、undo 和 redo 怎么记? ⭐⭐⭐⭐⭐

最简单:

undo

=

后悔药

用于:

我要反悔

↓

rollback

redo

=

备忘录

用于:

机器突然挂了

↓

重启

↓

恢复应该持久化的数据

所以:

undo

↓

回滚


redo

↓

崩溃恢复

这是目前阶段最重要的区别。


十二、Spring rollback 完整流程 ⭐⭐⭐⭐⭐

例如:

@Transactional(
    rollbackFor = Exception.class
)
public void createOrder() throws Exception {

    insertOrder();

    updateStock();

    throw new Exception();

}

完整理解:

调用createOrder()
      ↓
Spring代理对象
      ↓
事务拦截器
      ↓
开启数据库事务
      ↓
执行INSERT
      ↓
执行UPDATE
      ↓
发生Exception
      ↓
异常向外抛出
      ↓
事务拦截器收到异常
      ↓
检查rollbackFor
      ↓
Exception符合回滚规则
      ↓
Spring事务管理器执行rollback
      ↓
数据库收到rollback
      ↓
InnoDB利用自身事务机制
      ↓
撤销事务中的修改

十三、为什么 catch 异常会影响 rollback?

之前学事务失效的时候:

@Transactional
public void createOrder() {

    try {

        insertOrder();

        throw new RuntimeException();

    } catch (Exception e) {

        log.error("失败", e);

    }

}

现在可以从底层流程理解。

事务代理:

开启事务
   ↓
调用createOrder()
   ↓
方法正常返回

因为:

异常已经被catch掉

所以事务拦截器:

没有收到异常

于是可能:

commit

如果:

catch (Exception e) {

    log.error("失败", e);

    throw e;

}

流程:

发生异常
   ↓
catch
   ↓
重新throw
   ↓
异常传给事务拦截器
   ↓
判断回滚规则
   ↓
rollback

所以:

rollback 的关键之一,是事务拦截器能够感知到满足回滚规则的异常。


十四、rollbackFor 又是干什么的?

例如:

@Transactional(
    rollbackFor = Exception.class
)

意思:

如果方法抛出的异常

属于Exception及其子类

↓

满足配置的回滚规则

事务拦截器:

执行rollback

所以:

rollbackFor

不是自己执行回滚

而是:

告诉 Spring 哪些异常应该被判断为需要回滚。


十五、Propagation 和 rollback 的关系

例如:

事务A

里面调用:

事务B

如果:

Propagation.REQUIRED

那么:

A
+
B

↓

同一个事务

发生需要回滚的异常:

可能导致:

整个事务回滚

如果:

Propagation.REQUIRES_NEW

那么:

事务A

↓

挂起


事务B

↓

独立事务

B:

commit

以后:

A再:

rollback

B已经独立提交的事务:

不会因为A后续回滚而自动一起回滚

因为:

A和B是两个事务

十六、Isolation 和 rollback 的关系

Isolation:

READ_COMMITTED

REPEATABLE_READ

SERIALIZABLE

主要解决:

多个事务并发时

↓

彼此的数据可见性

而 rollback:

解决:

当前事务失败

↓

撤销自己的修改

所以:

Isolation

=

事务之间怎么隔离
Rollback

=

事务失败怎么撤销

不要混淆。


十七、把 @Transactional 完整拆开 ⭐⭐⭐⭐⭐

现在看到:

@Transactional(
    propagation = Propagation.REQUIRED,
    isolation = Isolation.REPEATABLE_READ,
    rollbackFor = Exception.class
)
public void createOrder() {

}

你应该能解释:


@Transactional

Spring声明式事务

底层:

AOP代理

propagation = REQUIRED

事务怎么传播

规则:

有事务 → 加入

没事务 → 创建

isolation = REPEATABLE_READ

事务之间怎么隔离

主要解决:

并发事务的数据可见性问题

rollbackFor = Exception.class

哪些异常应该触发回滚判断

最终:

发生符合规则的异常
      ↓
Spring事务拦截器
      ↓
rollback
      ↓
数据库
      ↓
真正撤销事务修改

十八、Spring事务完整知识链 ⭐⭐⭐⭐⭐

@Transactional
      ↓
Spring AOP
      ↓
动态代理
      ↓
事务拦截器
      ↓
事务管理器
      ↓
数据库事务

事务怎么传:

Propagation

事务怎么隔离:

Isolation

什么异常回滚:

rollbackFor

事务失败:

Spring
 ↓
rollback
 ↓
数据库
 ↓
撤销修改

MySQL底层:

undo log
 ↓
回滚 + MVCC历史版本
redo log
 ↓
崩溃恢复

十九、Spring 和 MySQL 的边界 ⭐⭐⭐⭐⭐

这个一定要理解。

Spring:

@Transactional

AOP

事务传播

事务管理器

commit / rollback控制

属于:

Spring

而:

undo log

redo log

MVCC

Read View

版本链

锁

属于:

MySQL / InnoDB

所以:

Spring负责管理事务

数据库负责真正实现事务

🎤 面试回答(30秒)

Spring 的 @Transactional 主要基于 AOP 代理实现。调用事务方法时,Spring 的事务拦截器会通过事务管理器开启事务。

如果方法正常执行完成,就提交事务;如果方法抛出了符合回滚规则的异常,就执行 rollback。

Spring 本身不会把之前执行的 SQL 反向执行,而是通知数据库回滚。真正的数据恢复由数据库事务机制完成。

例如 MySQL InnoDB 中,undo log 是事务回滚和 MVCC 历史版本的重要基础之一,而 redo log 主要用于崩溃恢复和保证持久性。


🎯 面试追问

Q1:@Transactional 发生异常后是谁负责回滚数据?

答:

Spring

↓

决定是否rollback

↓

调用事务管理器


数据库

↓

真正完成数据回滚

Q2:Spring 会把 SQL 反着执行吗?

答:

不会。

例如:

INSERT

失败以后:

Spring不会自己:

DELETE

而是:

Spring调用rollback

↓

数据库事务机制负责恢复

Q3:undo log 是什么?

答:

MySQL InnoDB 中用于事务回滚和维护 MVCC 历史版本的重要机制之一。


Q4:redo log 是什么?

答:

InnoDB 用于崩溃恢复、保证事务持久性的重要日志。


Q5:undo 和 redo 最大区别?

答:

undo

↓

回滚 / 历史版本


redo

↓

崩溃恢复

Q6:rollbackFor 是自己负责回滚吗?

答:

不是。

它主要:

告诉Spring

哪些异常满足回滚规则

真正执行:

rollback

的是事务管理机制。


Q7:为什么 catch 异常以后可能不回滚?

答:

因为:

异常被catch
      ↓
没有继续抛出
      ↓
事务拦截器没有感知到异常
      ↓
可能正常commit

Q8:REQUIRES_NEW 内层事务提交以后,外层回滚会把它回滚吗?

答:

不会自动一起回滚。

因为:

外层 → 事务A

内层 → 独立事务B

B已经独立提交。


Q9:Spring 和数据库在事务中分别负责什么?

答:

Spring:

事务管理

传播行为

回滚规则

commit / rollback控制

数据库:

真正的数据事务

隔离

锁

回滚

日志

MVCC

❓ 自测

  1. @Transactional 底层为什么能控制事务?

  2. Spring 事务正常结束后会执行什么?

  3. 发生符合回滚规则的异常后会执行什么?

  4. Spring 会自己把 SQL 反着执行吗?

  5. 真正负责恢复数据的是 Spring 还是数据库?

  6. undo log 是什么?

  7. undo log 除了回滚,还和什么机制有关?

  8. redo log 是什么?

  9. undo log 和 redo log 最大区别是什么?

  10. 为什么 catch 异常可能导致事务没有回滚?

  11. rollbackFor = Exception.class 是什么意思?

  12. rollbackFor 自己会执行 rollback 吗?

  13. REQUIRED 和 rollback 有什么关系?

  14. REQUIRES_NEW 已经提交后,外层事务回滚会自动把它一起回滚吗?

  15. Isolation 和 rollback 分别解决什么问题?

  16. Spring 和 MySQL 在事务中分别负责什么?


评论