Spring事务
事务的核心概念(传播行为、隔离级别)、底层实现原理,掌握事务失效场景、回滚规则和配置方式。
Spring 事务依赖 数据库事务(底层是数据库的事务支持,如 MySQL 的 InnoDB 引擎),若数据库不支持事务,Spring 事务无效。
一、事务核心:传播行为(7种)+ 隔离级别(4种)
事务的两大核心属性,决定了“事务之间如何交互”(传播行为)和“事务之间的隔离程度”(隔离级别),面试必背,重点突破传播行为的3种常用类型。
1. 事务传播行为
定义:当一个事务方法调用另一个事务方法时,如何决定事务的边界(新事务是否创建、当前事务是否参与),核心是“嵌套事务的行为规则”。
| 传播行为类型 | 核心含义(面试通俗答法) | 适用场景 | 是否新建事务 |
|---|---|---|---|
| REQUIRED(默认) | 如果当前有事务,就加入当前事务;如果没有,就新建一个事务。 | 绝大多数业务场景(如新增用户+新增用户日志) | 当前无事务则新建,有则加入 |
| REQUIRES_NEW | 无论当前是否有事务,都新建一个独立事务;原事务暂停,新事务执行完后,原事务继续。 | 需要独立提交/回滚的场景(如记录操作日志,即使主事务回滚,日志也要保留) | 必新建独立事务,与原事务隔离 |
| NESTED | 如果当前有事务,就在当前事务内新建一个“嵌套事务”(子事务);子事务依赖父事务,父事务回滚子事务也回滚,子事务回滚不影响父事务。 | 需要部分回滚的场景(如批量新增,其中一条失败,仅回滚该条,其余成功) | 不新建独立事务,嵌套在当前事务内 |
| SUPPORTS | 如果当前有事务,就加入;没有,就以非事务方式执行(不开启事务)。 | 可选事务场景(如查询接口,有事务则参与,无则不开启) | 不主动新建 |
| MANDATORY | 必须在当前有事务的情况下执行,否则抛出异常。 | 强制要求事务的场景(如核心转账操作) | 不新建,无事务则抛异常 |
| NOT_SUPPORTED | 无论当前是否有事务,都以非事务方式执行;若当前有事务,暂停原事务。 | 无需事务的场景(如纯查询,避免事务占用资源) | 不新建,有事务则暂停 |
| NEVER | 必须在无事务的情况下执行,否则抛出异常。 | 严禁事务的场景(如日志清理,避免锁表) | 不新建,有事务则抛异常 |
2. 重点:REQUIRED / REQUIRES_NEW / NESTED 区别 🔴
核心区分:事务独立性、回滚影响,用“嵌套场景示例”理解。
| 对比维度 | REQUIRED(默认) | REQUIRES_NEW | NESTED |
|---|---|---|---|
| 事务独立性 | 与父事务共用一个事务,无独立性 | 与父事务完全独立,两个事务互不影响 | 子事务依赖父事务,无独立事务,属于父事务的一部分 |
| 回滚影响 | 父/子事务一方回滚,另一方也会跟着回滚(同属一个事务) | 父事务回滚,不影响子事务(子事务已独立提交);子事务回滚,不影响父事务 | 父事务回滚,子事务必回滚;子事务回滚,父事务可选择继续执行(仅回滚子部分) |
| 嵌套场景示例(父方法A调用子方法B) | A和B共用一个事务,B抛异常回滚,A也回滚 | A开启事务,调用B时B新建独立事务,B提交后,A抛异常回滚,B的结果不回滚 | A开启事务,B嵌套在A中,B抛异常回滚,A可捕获异常继续执行,仅B的操作回滚 |
3. 事务隔离级别(4种,对应数据库隔离级别)
定义:解决“并发事务之间的干扰问题”,隔离级别越高,并发问题越少,但性能越低,Spring 支持数据库的4种隔离级别,默认跟随数据库(MySQL 默认为 REPEATABLE READ)。
| 隔离级别 | 核心作用(解决的并发问题) | 存在的问题 | 通俗说明 |
|---|---|---|---|
| READ UNCOMMITTED(读未提交) | 无隔离,允许读取未提交的数据 | 脏读、不可重复读、幻读(所有问题都存在) | 别人没提交的修改,我能读到 |
| READ COMMITTED(读已提交) | 禁止读取未提交的数据,解决脏读 | 不可重复读、幻读 | 别人提交后的修改,我才能读到,同一事务中多次读结果可能不同 |
| REPEATABLE READ(可重复读,MySQL 默认) | 解决脏读、不可重复读,保证同一事务中多次读结果一致 | 幻读(MySQL 用 MVCC 机制基本解决幻读) | 同一事务中,无论别人怎么修改提交,我读到的始终是事务开始时的数据 |
| SERIALIZABLE(串行化) | 完全隔离,事务串行执行,解决所有并发问题 | 性能极低,并发量差(会锁表) | 所有事务排队执行,相当于单线程,无并发干扰 |
“隔离级别越高,并发问题越少,性能越低”,以及 MySQL 默认隔离级别(REPEATABLE READ)。
二、Spring 事务底层原理(AOP + 动态代理) 🔴
核心逻辑:Spring 事务是 基于 AOP 动态代理 实现的,本质是“通过代理对象拦截目标方法,在方法执行前后织入事务的开启、提交、回滚逻辑”,与 AOP 原理一脉相承。
完整流程
-
容器启动时,Spring 扫描带有 @Transactional 注解的类/方法,生成代理对象(JDK 动态代理/ CGLIB 代理,与 AOP 代理逻辑一致);
-
当调用事务方法时,实际调用的是代理对象的方法,代理对象先拦截目标方法;
-
代理对象调用事务管理器(PlatformTransactionManager),开启数据库事务(执行 BEGIN);
-
代理对象调用目标方法,执行业务逻辑;
-
若目标方法正常执行(无异常),代理对象调用事务管理器提交事务(执行 COMMIT);
-
若目标方法抛出异常(符合回滚规则),代理对象调用事务管理器回滚事务(执行 ROLLBACK);
-
若异常不符合回滚规则,事务不回滚,直接提交。
关键提醒
-
事务管理器是核心:Spring 不直接管理事务,而是通过事务管理器适配不同数据库(如 MySQL 用 DataSourceTransactionManager);
-
与 AOP 关联:事务的“切面”是 @Transactional 注解,“切点”是被注解标记的方法,“通知”是事务的开启、提交、回滚逻辑;
-
代理对象:与 AOP 代理一致,有接口用 JDK 代理,无接口用 CGLIB 代理,这也是“内部调用事务失效”的核心原因(内部调用未走代理对象)。
三、事务失效的全部场景 🔴
核心原因:事务的 AOP 代理未生效、事务配置错误、数据库不支持事务,共7种场景,重点记前4种(最常见)。
1. 非 Public 方法(最易忽略)
场景:@Transactional 注解标注在 private、protected、default 修饰的方法上,事务失效。
原因:Spring AOP 默认只代理 public 方法,非 public 方法无法被 AOP 拦截,事务逻辑无法织入。
解决方案:将方法改为 public 修饰。
2. 内部调用(最常见)
场景:同一个 Bean 中,非事务方法 A 调用事务方法 B,B 的事务失效。
|
|
原因:this 指向目标对象,而非代理对象,内部调用未触发 AOP 拦截,事务逻辑未织入。
解决方案:通过 ApplicationContext 获取代理对象,或 @Autowired 注入自身(代理对象),再调用方法。
3. 异常类型错误(最易踩坑)
场景:事务方法抛出 checked 异常(如 IOException、SQLException),事务不回滚;或手动捕获异常,未重新抛出。
原因:Spring 事务默认只对 unchecked 异常(RuntimeException 及其子类) 回滚,checked 异常不回滚;若手动捕获异常,未重新抛出,代理对象无法感知异常,无法触发回滚。
解决方案:① 抛出 unchecked 异常;② 用 @Transactional(rollbackFor = 异常类型.class) 指定回滚异常;③ 捕获异常后,重新抛出异常(throw new RuntimeException(e))。
4. 数据库引擎不支持事务
场景:MySQL 数据库使用 MyISAM 引擎,事务失效。
原因:MyISAM 引擎是非事务引擎,不支持事务;Spring 事务依赖数据库事务,数据库不支持,事务无效。
解决方案:将数据库引擎改为 InnoDB(MySQL 5.5+ 默认是 InnoDB)。
5. 未开启 Spring 事务管理
场景:未加 @EnableTransactionManagement 注解(Spring Boot 自动配置可省略,但手动配置需加),事务失效。
原因:未开启 Spring 事务管理功能,无法扫描 @Transactional 注解,无法生成代理对象。
解决方案:在配置类上加 @EnableTransactionManagement 注解。
6. @Transactional 注解配置错误
-
propagation = NOT_SUPPORTED / NEVER:配置为非事务传播行为,事务不开启;
-
readOnly = true:配置为只读事务,若执行修改操作(insert/update/delete),事务失效;
-
rollbackFor 配置错误:未指定需要回滚的异常类型,导致异常不回滚。
7. 目标对象不是 Spring 容器管理的 Bean
场景:手动 new 出来的对象(如 new UserService()),而非通过 @Autowired 获取的 Bean,事务失效。
原因:手动 new 的对象未被 Spring IOC 管理,无法生成代理对象,事务逻辑无法织入。
解决方案:通过 @Autowired 注入 Bean,或通过 ApplicationContext 获取 Bean。
四、事务配置方式:@Transactional 注解 + 编程式事务对比
Spring 支持两种事务配置方式,实际开发中 注解式事务(@Transactional)最常用,编程式事务用于复杂场景,重点掌握注解式。
1. 注解式事务(推荐,简洁高效)
核心:用 @Transactional 注解标注类/方法,Spring 自动织入事务逻辑,可配置传播行为、隔离级别、回滚规则等。
|
|
关键:方法级别注解优先级高于类级别,常用配置参数:propagation(传播行为)、isolation(隔离级别)、rollbackFor(回滚异常)、readOnly(只读)。
2. 编程式事务(手动控制,复杂场景)
核心:通过 TransactionTemplate 手动控制事务的开启、提交、回滚,适用于需要灵活控制事务的场景(如多事务嵌套、条件提交)。
|
|
3. 两种方式对比 🔴
| 对比维度 | 注解式事务(@Transactional) | 编程式事务(TransactionTemplate) |
|---|---|---|
| 使用复杂度 | 简单,只需加注解,无需手动控制 | 复杂,需手动写代码控制提交/回滚 |
| 灵活性 | 低,无法灵活控制事务流程(如条件回滚) | 高,可灵活控制事务的开启、提交、回滚时机 |
| 适用场景 | 绝大多数简单业务场景(CRUD) | 复杂场景(多事务嵌套、条件提交/回滚) |
| 耦合度 | 低,注解式无侵入 | 高,代码侵入性强 |
五、事务回滚规则 🔴
核心:明确“哪些异常会回滚、哪些不会”,以及“如何自定义回滚规则”,避免因异常配置错误导致事务不回滚。
1. 默认回滚规则(重点)
-
✅ 会回滚:所有 unchecked 异常(RuntimeException 及其子类,如 NullPointerException、IllegalArgumentException);
-
❌ 不会回滚:所有 checked 异常(需要手动捕获的异常,如 IOException、SQLException、ClassNotFoundException);
-
❌ 不会回滚:手动捕获异常,且未重新抛出异常(代理对象无法感知异常)。
2. 自定义回滚规则(常用)
通过 @Transactional 注解的两个参数,自定义回滚/不回滚的异常:
|
|
关键提醒:rollbackFor 优先级高于默认规则,建议日常开发中显式配置 rollbackFor = Exception.class,避免因异常类型问题导致事务不回滚。
3. 特殊场景:环绕通知与事务回滚
若用 AOP 环绕通知拦截事务方法,需注意:环绕通知中捕获异常后,必须重新抛出,否则事务无法回滚。
|
|
总结 🔴
-
事务核心:7种传播行为(重点 REQUIRED/REQUIRES_NEW/NESTED)、4种隔离级别(MySQL 默认 REPEATABLE READ);
-
底层原理:AOP + 动态代理,代理对象拦截目标方法,织入事务开启、提交、回滚逻辑;
-
事务失效场景(必背):非 public 方法、内部调用、异常类型错误、数据库引擎不支持、未开启事务管理、注解配置错误、非 Spring 管理的 Bean;
-
配置方式:注解式(@Transactional,常用)、编程式(TransactionTemplate,复杂场景);
-
回滚规则:默认回滚 unchecked 异常,可通过 rollbackFor/noRollbackFor 自定义,环绕通知需重新抛出异常。