
Spring Framework 事务管理 系统性知识体系
一、事务管理基础:ACID特性
事务是数据库操作的最小执行单元,要么全部成功,要么全部失败。ACID是事务的四大基本特性,是事务管理的理论基石。
| 特性 | 英文全称 | 核心含义 | 实现机制 |
|---|---|---|---|
| 原子性 | Atomicity | 事务是不可分割的整体,所有操作要么全部执行,要么全部回滚 | 数据库的Undo Log(回滚日志)记录数据修改前的状态,异常时回滚到原始状态 |
| 一致性 | Consistency | 事务执行前后,数据库的完整性约束(主键、外键、唯一索引等)不被破坏 | 由原子性、隔离性、持久性共同保证,同时依赖应用层业务逻辑校验 |
| 隔离性 | Isolation | 多个并发事务之间相互隔离,互不干扰 | 数据库通过锁机制和MVCC(多版本并发控制)实现不同的隔离级别 |
| 持久性 | Durability | 事务一旦提交,对数据库的修改就是永久性的,即使系统崩溃也不会丢失 | 数据库的Redo Log(重做日志)记录数据修改后的状态,系统重启时恢复已提交事务 |
关键区分:原子性关注"事务内操作的整体性",一致性关注"数据库状态的正确性",隔离性关注"并发事务间的互不干扰",持久性关注"数据的永久保存"。
二、事务隔离级别
隔离级别解决的是并发事务之间的相互影响问题。隔离级别越高,并发性能越低,但数据一致性越好。
2.1 并发事务导致的三大问题
- 脏读:一个事务读取了另一个事务未提交的修改数据
- 不可重复读:同一个事务内,两次读取同一行数据,结果不同(因为中间有其他事务提交了修改)
- 幻读:同一个事务内,两次执行相同的查询,结果集行数不同(因为中间有其他事务提交了插入/删除)
| 问题 | 描述 |
|---|---|
| 脏读(Dirty Read) | 一个事务读取了另一个事务未提交的修改数据 |
| 不可重复读(Non-Repeatable Read) | 同一个事务内,对同一行数据的两次读取结果不一致(因为中间被其他事务修改并提交) |
| 幻读(Phantom Read) | 同一个事务内,两次执行相同的查询语句,第二次查询返回了第一次没有的行(因为中间被其他事务插入了新数据并提交) |
2.2 标准SQL定义的四个隔离级别
| 隔离级别 | 脏读 | 不可重复读 | 幻读 | 数据库默认级别/说明 |
|---|---|---|---|---|
READ_UNCOMMITTED(读未提交) |
✅ | ✅ | ✅ | 最低级别,允许读取未提交的数据,几乎不使用 |
READ_COMMITTED(读已提交) |
❌ | ✅ | ✅ | 大多数数据库的默认级别(Oracle、SQL Server),只能读取已提交的数据 |
REPEATABLE_READ(可重复读) |
❌ | ❌ | ✅ | MySQL InnoDB的默认级别,保证同一个事务内多次读取同一行数据的结果一致 |
SERIALIZABLE(串行化) |
❌ | ❌ | ❌ | 最高级别,强制事务串行执行,完全避免并发问题,但性能极差 |
MySQL特殊说明:InnoDB引擎的可重复读级别通过Next-Key Lock(间隙锁+行锁) 解决了幻读问题,这是MySQL对标准SQL的扩展。
2.3 Spring中的隔离级别配置
Spring通过Isolation枚举定义了5种隔离级别:
public enum Isolation {
DEFAULT(-1), // 使用数据库默认隔离级别
READ_UNCOMMITTED(1),
READ_COMMITTED(2),
REPEATABLE_READ(4),
SERIALIZABLE(8);
}
使用方式:
@Transactional(isolation = Isolation.REPEATABLE_READ)
public void updateUser() {
// 业务逻辑
}
三、事务传播行为
传播行为是Spring框架独有的特性,定义了多个事务方法相互调用时,事务如何在这些方法之间传播的规则。Spring共定义了7种传播行为,可分为三类。
3.1 核心传播行为详解
1. 支持当前事务的传播行为
| 传播行为 | 说明 |
|---|---|
REQUIRED(默认) |
如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务 |
SUPPORTS |
如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务方式执行 |
MANDATORY |
如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常 |
2. 不支持当前事务的传播行为
| 传播行为 | 说明 |
|---|---|
REQUIRES_NEW |
总是创建一个新的事务;如果当前存在事务,则将当前事务挂起 |
NOT_SUPPORTED |
总是以非事务方式执行;如果当前存在事务,则将当前事务挂起 |
NEVER |
总是以非事务方式执行;如果当前存在事务,则抛出异常 |
3. 嵌套事务
| 传播行为 | 说明 |
|---|---|
NESTED |
如果当前存在事务,则在嵌套事务内执行;如果当前没有事务,则创建一个新的事务 |
| 传播行为 | 含义 | 存在事务时 | 不存在事务时 | 适用场景 |
|---|---|---|---|---|
| REQUIRED(默认) | 必须有事务 | 加入当前事务 | 新建一个事务 | 绝大多数业务场景 |
| REQUIRES_NEW | 必须有新事务 | 挂起当前事务,新建独立事务 | 新建一个事务 | 日志记录、审计等需要独立提交的操作 |
| SUPPORTS | 支持事务 | 加入当前事务 | 以非事务方式执行 | 查询操作 |
| NOT_SUPPORTED | 不支持事务 | 挂起当前事务,以非事务方式执行 | 以非事务方式执行 | 不需要事务的查询操作,提高性能 |
| MANDATORY | 强制有事务 | 加入当前事务 | 抛出异常 | 必须在事务中执行的方法,防止被误调用 |
| NEVER | 禁止有事务 | 抛出异常 | 以非事务方式执行 | 绝对不能在事务中执行的方法 |
| NESTED | 嵌套事务 | 在当前事务内创建嵌套事务(保存点) | 新建一个事务 | 复杂业务中需要部分回滚的场景 |
3.2 关键传播行为对比
REQUIRED vs REQUIRES_NEW
- REQUIRED:多个方法共享同一个事务,任何一个方法异常都会导致整个事务回滚
- REQUIRES_NEW:每个方法都有自己独立的事务,内部方法异常不会影响外部事务,外部事务异常也不会回滚内部已提交的事务
NESTED vs REQUIRES_NEW
- NESTED:嵌套事务是外部事务的子事务,外部事务回滚会导致嵌套事务回滚;嵌套事务回滚不会影响外部事务(只会回滚到保存点)
- REQUIRES_NEW:完全独立的事务,互不影响
四、@Transactional 底层原理
@Transactional注解是Spring声明式事务的核心,其底层基于AOP(面向切面编程) 和动态代理实现。
4.1 整体执行流程
代理创建:Spring容器启动时,扫描所有带有
@Transactional注解的类和方法,为其创建动态代理对象- 目标类实现了接口:使用JDK动态代理
- 目标类没有实现接口:使用CGLIB动态代理
方法调用:当调用目标方法时,实际调用的是代理对象的方法
- 事务拦截:代理对象的方法会被
TransactionInterceptor拦截 - 事务开启:
TransactionInterceptor调用PlatformTransactionManager获取事务状态,开启事务 - 方法执行:执行目标方法的业务逻辑
- 事务提交/回滚:
- 方法正常执行完成:提交事务
- 方法抛出未被捕获的异常:根据配置判断是否回滚
4.2 核心组件
PlatformTransactionManager:事务管理器,Spring事务的核心接口,定义了事务的基本操作
public interface PlatformTransactionManager { TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException; void commit(TransactionStatus status) throws TransactionException; void rollback(TransactionStatus status) throws TransactionException; }- 不同持久化框架有不同的实现:
- JDBC/MyBatis:
DataSourceTransactionManager - JPA/Hibernate:
JpaTransactionManager - JTA分布式事务:
JtaTransactionManager
- JDBC/MyBatis:
- 不同持久化框架有不同的实现:
TransactionDefinition:事务定义信息,包含隔离级别、传播行为、超时时间、只读属性等
- TransactionStatus:事务状态信息,用于判断事务是否是新事务、是否有保存点等
4.3 事务回滚规则
- 默认规则:只有当方法抛出运行时异常(RuntimeException) 和错误(Error) 时,才会回滚事务
- 自定义回滚规则:
// 指定回滚的异常类型 @Transactional(rollbackFor = Exception.class) // 指定不回滚的异常类型 @Transactional(noRollbackFor = NullPointerException.class)
五、@Transactional失效场景及解决方案
@Transactional注解失效是Spring事务管理中最常见的问题,绝大多数失效场景都源于动态代理机制的局限性。以下是最常见的10种失效场景及解决方案:
1. 方法不是public的
- 失效原因:Spring AOP只能拦截public方法,private、protected、default方法上的
@Transactional注解无效 - 解决方案:将事务方法改为public
2. 同一个类内部方法调用
- 失效原因:在同一个类中,一个方法调用另一个标注了
@Transactional的方法,实际上是通过this调用目标方法,而不是通过代理对象调用,因此不会被事务拦截器拦截 - 解决方案:
- 注入自己的代理对象:
@Autowired private UserService userService;然后通过userService.methodB()调用 - 使用
AopContext.currentProxy()获取当前代理对象:((UserService) AopContext.currentProxy()).methodB(); - 将事务方法拆分到不同的类中
- 注入自己的代理对象:
3. 异常类型不正确
- 失效原因:Spring默认只对运行时异常(RuntimeException)和错误(Error)进行回滚,对受检异常(Checked Exception)不回滚
- 解决方案:
// 对所有异常进行回滚 @Transactional(rollbackFor = Exception.class) // 对指定异常进行回滚 @Transactional(rollbackFor = { IOException.class, SQLException.class})
4. 异常被捕获但没有重新抛出
- 失效原因:如果在业务方法中捕获了异常但没有重新抛出,事务拦截器会认为方法执行成功,不会触发回滚
- 解决方案:捕获异常后重新抛出,或者手动设置事务回滚
@Transactional public void updateUser() { try { // 业务逻辑 } catch (Exception e) { // 手动设置事务回滚 TransactionAspectSupport.currentTransactionStatus().setRollbackOnly(); } }
5. 数据库引擎不支持事务
- 失效原因:MySQL的MyISAM引擎不支持事务,只有InnoDB引擎支持事务
- 解决方案:将数据库表的引擎改为InnoDB
6. 事务传播行为配置错误
- 失效原因:使用了不支持事务的传播行为,如
SUPPORTS、NOT_SUPPORTED、NEVER - 解决方案:根据业务需求正确配置传播行为,大多数情况下使用默认的
REQUIRED即可
7. 多线程环境下
- 失效原因:Spring事务上下文是通过
ThreadLocal绑定到当前线程的,多线程环境下,子线程无法获取父线程的事务上下文 - 解决方案:避免在事务方法中开启新线程执行数据库操作,或者使用分布式事务
8. 方法被final或static修饰
- 失效原因:final方法无法被重写,static方法属于类而不是对象,都无法被CGLIB动态代理
- 解决方案:将事务方法改为非final、非static的public方法
9. 没有被Spring容器管理
- 失效原因:如果类没有被
@Component、@Service等注解标注,没有被Spring容器管理,Spring无法为其创建代理对象 - 解决方案:将类添加到Spring容器中
10. 错误的事务管理器
- 失效原因:如果项目中有多个数据源,配置了多个事务管理器,但没有指定使用哪个事务管理器
- 解决方案:在
@Transactional注解中指定事务管理器的名称@Transactional(transactionManager = "userTransactionManager") public void updateUser() { // 业务逻辑 }
六、事务管理最佳实践
- 优先使用声明式事务:除非需要细粒度的事务控制,否则优先使用
@Transactional注解 - 合理设置事务属性:
- 只读事务:对于查询方法,设置
readOnly = true可以提高性能 - 事务超时:设置
timeout属性,避免事务长时间占用数据库连接 - 隔离级别:根据业务需求选择合适的隔离级别,不要盲目使用最高级别
- 只读事务:对于查询方法,设置
- 避免大事务:大事务会占用数据库连接时间过长,影响系统并发性能,应将大事务拆分为多个小事务
- 正确处理异常:明确指定回滚的异常类型,避免异常被捕获后不回滚的问题
- 避免在事务方法中执行耗时操作:如网络调用、文件读写等,这些操作会延长事务执行时间
- 分布式事务:对于跨多个数据源或微服务的事务,应使用分布式事务解决方案,如Seata、XA等
- 避免方法内部调用事务方法:如果必须调用,使用代理对象
- 使用
@Transactional(readOnly = true)优化查询操作:告诉数据库这是只读事务,可以优化查询性能
Spring Framework 事务管理 面试高频问答卡片
(按面试出现频率排序,核心考点全覆盖,可直接背诵)
最高频必考题
Q1:@Transactional注解失效的常见场景有哪些?如何解决?
标准答案:
@Transactional失效绝大多数源于动态代理机制的局限性,常见10种场景:
- 方法不是public的
- 原因:Spring AOP只能拦截public方法
- 解决:将事务方法改为public
- 同一个类内部方法调用
- 原因:通过
this调用目标方法,不是通过代理对象,不会被事务拦截器拦截 - 解决:①注入自己的代理对象调用;②使用
AopContext.currentProxy()获取代理对象;③拆分到不同类
- 原因:通过
- 异常类型不正确
- 原因:默认只回滚
RuntimeException和Error,受检异常(如IOException)不回滚 - 解决:添加
@Transactional(rollbackFor = Exception.class)
- 原因:默认只回滚
- 异常被捕获但没有重新抛出
- 原因:事务拦截器认为方法执行成功,不会触发回滚
- 解决:①捕获后重新抛出;②手动设置回滚:
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly()
- 数据库引擎不支持事务
- 原因:MySQL的MyISAM引擎不支持事务
- 解决:将表引擎改为InnoDB
- 事务传播行为配置错误
- 原因:使用了
SUPPORTS、NOT_SUPPORTED、NEVER等不支持事务的传播行为 - 解决:大多数业务场景使用默认的
REQUIRED即可
- 原因:使用了
- 多线程环境下
- 原因:Spring事务上下文通过
ThreadLocal绑定到当前线程,子线程无法获取 - 解决:避免在事务方法中开启新线程执行数据库操作,或使用分布式事务
- 原因:Spring事务上下文通过
- 方法被final或static修饰
- 原因:final方法无法被重写,static方法属于类,都无法被CGLIB动态代理
- 解决:将事务方法改为非final、非static的public方法
- 类没有被Spring容器管理
- 原因:没有添加
@Component、@Service等注解,Spring无法创建代理对象 - 解决:将类添加到Spring容器中
- 原因:没有添加
- 多个事务管理器未指定
- 原因:项目中有多个数据源和事务管理器,Spring不知道使用哪个
- 解决:在
@Transactional中指定transactionManager属性
Q2:Spring事务的传播行为是什么?有哪几种?分别解释一下?
标准答案:
传播行为是Spring框架独有的特性,定义了多个事务方法相互调用时,事务如何在这些方法之间传播的规则。共7种,分为三类:
支持当前事务
- REQUIRED(默认):有事务就加入,没有就新建。适用于绝大多数业务场景。
- SUPPORTS:有事务就加入,没有就以非事务方式执行。适用于查询操作。
- MANDATORY:有事务就加入,没有就抛出异常。用于强制必须在事务中执行的方法。
不支持当前事务
- REQUIRES_NEW:总是新建独立事务,有事务就挂起当前事务。适用于日志、审计等需要独立提交的操作。
- NOT_SUPPORTED:总是以非事务方式执行,有事务就挂起。适用于不需要事务的查询,提高性能。
- NEVER:总是以非事务方式执行,有事务就抛出异常。用于绝对不能在事务中执行的方法。
嵌套事务
- NESTED:有事务就创建嵌套事务(保存点),没有就新建事务。适用于需要部分回滚的复杂业务场景。
Q3:REQUIRED和REQUIRES_NEW传播行为的区别是什么?
标准答案:
核心区别在于事务的独立性:
- REQUIRED:多个方法共享同一个事务。任何一个方法抛出异常,都会导致整个事务回滚。
- REQUIRES_NEW:每个方法都有自己独立的事务。内部方法异常不会影响外部事务,外部事务异常也不会回滚内部已提交的事务。
举例:方法A调用方法B
- B使用REQUIRED:A和B在同一个事务中,B异常会导致A和B都回滚;A异常也会导致A和B都回滚。
- B使用REQUIRES_NEW:A和B是两个独立事务,B异常只会回滚B;A异常只会回滚A,不会影响已提交的B。
高频核心题
Q4:什么是事务?事务的ACID特性分别是什么?
标准答案:
事务是数据库操作的最小执行单元,保证一系列操作要么全部成功执行,要么全部失败回滚。ACID是事务的四大基本特性:
- 原子性(Atomicity):事务是不可分割的整体,所有操作要么全部执行,要么全部回滚。通过数据库的Undo Log(回滚日志)实现。
- 一致性(Consistency):事务执行前后,数据库的完整性约束(主键、外键、唯一索引等)不被破坏。由原子性、隔离性、持久性共同保证,同时依赖应用层业务逻辑校验。
- 隔离性(Isolation):多个并发事务之间相互隔离,互不干扰。通过数据库的锁机制和MVCC(多版本并发控制)实现不同的隔离级别。
- 持久性(Durability):事务一旦提交,对数据库的修改就是永久性的,即使系统崩溃也不会丢失。通过数据库的Redo Log(重做日志)实现。
Q5:并发事务会导致哪些问题?分别解释一下?
标准答案:
并发事务执行时,如果没有足够的隔离性,会导致以下三类问题:
- 脏读:一个事务读取了另一个事务未提交的修改数据。如果另一个事务回滚,读取到的数据就是无效的。
- 不可重复读:同一个事务内,两次读取同一行数据,结果不同。因为中间有其他事务对该行数据进行了修改并提交。
- 幻读:同一个事务内,两次执行相同的查询语句,结果集行数不同。因为中间有其他事务执行了插入或删除操作并提交。
关键区分:不可重复读针对数据修改,幻读针对数据新增/删除。
Q6:标准SQL定义了哪四个事务隔离级别?分别能解决哪些问题?
标准答案:
标准SQL定义了四个从低到高的隔离级别,级别越高,数据一致性越好,但并发性能越低:
- READ_UNCOMMITTED(读未提交):最低级别,允许读取未提交的数据。不能解决任何并发问题。
- READ_COMMITTED(读已提交):只能读取已提交的数据。解决了脏读问题。是Oracle、SQL Server等大多数数据库的默认级别。
- REPEATABLE_READ(可重复读):保证同一个事务内多次读取同一行数据的结果一致。解决了脏读和不可重复读问题。
- SERIALIZABLE(串行化):最高级别,强制事务串行执行。解决了脏读、不可重复读和幻读所有问题,但性能极差。
Q7:MySQL InnoDB的默认隔离级别是什么?它是如何解决幻读问题的?
标准答案:
MySQL InnoDB引擎的默认隔离级别是REPEATABLE_READ(可重复读)。
标准SQL的可重复读级别无法解决幻读问题,但InnoDB通过Next-Key Lock(临键锁) 机制解决了幻读。Next-Key Lock是行锁+间隙锁的组合:
- 行锁:锁定已经存在的记录行
- 间隙锁:锁定记录之间的间隙,防止其他事务在间隙中插入新数据
通过临键锁,InnoDB在可重复读级别下就可以避免幻读问题,这是MySQL对标准SQL的重要扩展。
Q8:@Transactional注解的底层实现原理是什么?
标准答案:
@Transactional注解是Spring声明式事务的核心,其底层基于AOP(面向切面编程) 和动态代理实现,整体执行流程如下:
- 代理创建:Spring容器启动时,扫描所有带有@Transactional注解的类和方法,为其创建动态代理对象。
- 目标类实现了接口:使用JDK动态代理
- 目标类没有实现接口:使用CGLIB动态代理
- 方法拦截:当调用目标方法时,实际调用的是代理对象的方法,会被
TransactionInterceptor(事务拦截器)拦截。 - 事务开启:事务拦截器调用
PlatformTransactionManager(事务管理器)获取事务状态,开启事务。 - 方法执行:执行目标方法的业务逻辑。
- 事务提交/回滚:
- 方法正常执行完成:提交事务
- 方法抛出未被捕获的异常:根据配置判断是否回滚
常考基础题
Q9:NESTED和REQUIRES_NEW传播行为的区别是什么?
标准答案:
核心区别在于与外部事务的关联关系:
- NESTED(嵌套事务):是外部事务的子事务,依赖于外部事务。
- 外部事务回滚:会导致嵌套事务一起回滚
- 嵌套事务回滚:只会回滚到自己的保存点,不会影响外部事务
- REQUIRES_NEW(新建事务):是完全独立的事务,与外部事务没有任何关联。
- 外部事务回滚:不会影响内部已提交的事务
- 内部事务回滚:不会影响外部事务
Q10:Spring事务管理的核心组件有哪些?分别有什么作用?
标准答案:
Spring事务管理有三个核心接口:
- PlatformTransactionManager(事务管理器):Spring事务的核心接口,定义了事务的基本操作(获取事务、提交、回滚)。不同持久化框架有不同的实现:
- JDBC/MyBatis:
DataSourceTransactionManager - JPA/Hibernate:
JpaTransactionManager - 分布式事务:
JtaTransactionManager
- JDBC/MyBatis:
- TransactionDefinition(事务定义):包含事务的所有配置信息,如隔离级别、传播行为、超时时间、只读属性等。
- TransactionStatus(事务状态):记录事务的运行状态,如是否是新事务、是否有保存点、是否已标记回滚等。
Q11:@Transactional注解默认的回滚规则是什么?如何自定义?
标准答案:
- 默认回滚规则:只有当方法抛出运行时异常(RuntimeException) 和错误(Error) 时,才会回滚事务。对于受检异常(Checked Exception,如
IOException、SQLException等),默认不会回滚。 - 自定义回滚规则:通过两个属性配置:
rollbackFor:指定需要回滚的异常类型@Transactional(rollbackFor = Exception.class) // 对所有异常回滚noRollbackFor:指定不需要回滚的异常类型@Transactional(noRollbackFor = NullPointerException.class)
Q12:Spring事务管理有哪些最佳实践?
标准答案:
- 优先使用声明式事务:除非需要细粒度的事务控制,否则优先使用@Transactional注解
- 合理设置事务属性:
- 查询方法设置
readOnly = true,提高数据库性能 - 设置合理的
timeout超时时间,避免事务长时间占用连接 - 根据业务需求选择合适的隔离级别,不要盲目使用最高级别
- 查询方法设置
- 避免大事务:大事务会占用连接时间过长,影响并发性能,应拆分为多个小事务
- 正确处理异常:明确指定
rollbackFor属性,避免异常被捕获后不回滚 - 避免在事务中执行耗时操作:如网络调用、文件读写等,会延长事务执行时间
- 避免内部调用事务方法:如果必须调用,使用代理对象
- 分布式事务场景:跨数据源或微服务的事务,使用Seata等分布式事务解决方案