Spring 事务机制详解:原理、传播行为与失效场景

目录

一、对事务的理解

[二、Spring 提供了哪几种事务管理方式?](#二、Spring 提供了哪几种事务管理方式?)

[1. 编程式事务](#1. 编程式事务)

[2. 声明式事务(主流方式)](#2. 声明式事务(主流方式))

三、声明式事务的底层原理

[四、Spring 中事务为什么会失效?](#四、Spring 中事务为什么会失效?)

[1. 方法访问权限问题](#1. 方法访问权限问题)

[2. 方法被 final 修饰](#2. 方法被 final 修饰)

[3. 方法内部调用(最常见)](#3. 方法内部调用(最常见))

[4. Bean 未被 Spring 管理](#4. Bean 未被 Spring 管理)

[5. 多线程调用](#5. 多线程调用)

[6. 数据表不支持事务](#6. 数据表不支持事务)

[7. 传播行为配置不当](#7. 传播行为配置不当)

[8. 异常被吞掉](#8. 异常被吞掉)

[9. 抛出了不回滚的异常](#9. 抛出了不回滚的异常)

[10. 自定义了回滚规则但配置错误](#10. 自定义了回滚规则但配置错误)

[11. 嵌套事务回滚预期错误](#11. 嵌套事务回滚预期错误)

五、事务传播行为详解

六、事务隔离级别

七、总结


事务是后端开发中一个绕不开的话题。

在 Spring 项目中,我们几乎每天都在使用 @Transactional,但一旦事务"不生效",问题往往就开始变得棘手。

这篇文章从事务的本质认知出发,系统梳理:

  • Spring 事务的两种管理方式
  • 声明式事务的底层原理
  • 常见事务失效场景
  • 事务传播行为
  • 事务隔离级别的取舍

一、对事务的理解

从本质上讲,事务是一组操作的逻辑单元,这组操作要么全部成功,要么全部失败。

数据库事务需要满足 ACID 特性:

  • 原子性(Atomicity):要么全做,要么全不做
  • 一致性(Consistency):事务前后数据状态一致
  • 隔离性(Isolation):事务之间互不干扰
  • 持久性(Durability):提交后的结果永久生效

Spring 本身并不实现事务,它只是对数据库事务进行了统一管理和封装。

二、Spring 提供了哪几种事务管理方式?

Spring 中的事务管理可以分为两大类:编程式事务声明式事务

1. 编程式事务

编程式事务允许我们在代码中显式控制事务的边界,常见方式有:

  • TransactionTemplate
  • PlatformTransactionManager
java 复制代码
transactionTemplate.execute(status -> {
    // 业务逻辑
    return null;
});

优点:

  • 灵活
  • 可以精确控制到代码块级别

缺点:

  • 侵入业务代码
  • 可读性差
  • 不适合复杂业务

在实际项目中使用较少,更多用于特殊场景。

2. 声明式事务(主流方式)

声明式事务是 Spring 中最常用的事务管理方式

java 复制代码
@Transactional
public void save() {
    // 业务逻辑
}

特点:

  • 不侵入业务代码
  • 只需通过注解声明事务
  • Spring 自动管理事务的开启、提交和回滚

声明式事务是 Spring 事务体系的核心。

三、声明式事务的底层原理

Spring 的声明式事务并不是"魔法",而是基于 AOP + 代理机制 实现的。

整个过程可以分为两个阶段。

第一阶段:容器启动阶段

  • Spring 启动时扫描 Bean
  • 发现方法上存在 @Transactional
  • 不会直接返回原始 Bean
  • 而是为该 Bean 创建一个 代理对象

第二阶段:方法运行阶段

  • 实际调用的是 代理对象的方法
  • 事务拦截器在方法执行前介入
  • 根据 @Transactional 配置:
    • 传播行为
    • 隔离级别
    • 回滚规则
  • 通过事务管理器开启事务
  • 方法正常结束 → 提交事务
  • 方法抛出异常 → 回滚事务

关键点:事务是加在代理对象上的,而不是原始对象。

四、Spring 中事务为什么会失效?

这是事务问题中最容易踩坑的一部分

下面是常见的事务不生效场景汇总。

1. 方法访问权限问题

  • 非 public 方法
  • Spring 默认基于代理,无法增强

2. 方法被 final 修饰

  • CGLIB 无法覆盖 final 方法
  • 事务增强失败

3. 方法内部调用(最常见)

java 复制代码
public void methodA() {
    methodB(); // 事务失效
}

@Transactional
public void methodB() {
}

原因:内部调用绕过了代理对象

4. Bean 未被 Spring 管理

  • 手动 new 出来的对象
  • 容器感知不到,自然无事务

5. 多线程调用

  • 事务基于线程绑定
  • 新线程中事务上下文丢失

6. 数据表不支持事务

  • 如 MySQL 的 MyISAM 引擎

7. 传播行为配置不当

  • 事务被挂起
  • 新事务被强制创建

8. 异常被吞掉

java 复制代码
try {
    save();
} catch (Exception e) {
    // 没抛出
}
  • Spring 认为方法正常结束
  • 不回滚

9. 抛出了不回滚的异常

  • 默认只回滚 RuntimeException
  • 受检异常需显式配置

10. 自定义了回滚规则但配置错误

11. 嵌套事务回滚预期错误

  • 子事务回滚不等于父事务回滚

五、事务传播行为详解

事务传播行为描述的是:一个事务方法调用另一个事务方法时,事务该如何处理

Spring 一共提供 7 种传播行为

  1. REQUIRED(默认):如果当前没有事务,则新建一个事务;如果已经存在一个事务,则加入到这个事务中
  2. SUPPORTS:如果当前有事务,则加入到这个事务;如果没有事务,则不使用事务
  3. REQUIRES_NEW:无论对当前是否有事务,都会新建一个事务
  4. MANDATORY:如果当前有事务,则加入这个事务;如果没有事务,则抛出异常
  5. NOT_SUPPORTED:总是非事务的执行,并挂起任何存在的事务
  6. NEVER:如果当前有事务,则抛出异常;如果没有事务,则以非事务方法执行
  7. NESTED:如果当前有事务,则在当前事务中嵌套一个事务;如果没有事务,则新建一个事务

六、事务隔离级别

事务隔离级别决定了并发事务之间的可见性

隔离级别 能解决的问题
READ UNCOMMITTED 几乎不解决
READ COMMITTED 避免脏读
REPEATABLE READ 避免脏读、不可重复读
SERIALIZABLE 避免所有并发问题

**安全性:**Serializable > Repeatable read > Read committed > Read committed

**性能:**Serializable < Repeatable read < Read committed < Read committed

大多数情况下,使用数据库默认隔离级别即可。

七、总结

Spring 事务的核心从来不在注解,而在于:代理对象 + AOP 拦截 + 异常控制

Spring 事务,本质是基于 AOP 的方法级事务管理机制。

相关推荐
风筝在晴天搁浅2 小时前
hot100 102.二叉树的层序遍历
java·算法
IT大白2 小时前
8、MySQL相关问题补充
数据库·sql
爪哇天下2 小时前
Mysql实现经纬度距离的排序(粗略的城市排序)
数据库·mysql
独自破碎E2 小时前
MySQL中有哪些日志类型?
数据库·mysql
笨蛋不要掉眼泪2 小时前
Redis核心数据类型与命令
数据库·redis·缓存
lina_mua2 小时前
Cursor模型选择完全指南:为前端开发找到最佳AI助手
java·前端·人工智能·编辑器·visual studio
秋92 小时前
idea中如何使用Trae AI插件,并举例说明
java·人工智能·intellij-idea
输出输入2 小时前
JAVA中return和break区别
java
董世昌412 小时前
null和undefined的区别是什么?
java·前端·javascript