Spring事务细探
Spring 事务详解
Spring 事务里面分为“物理事务”和“逻辑事务”:
- 所谓的“物理事务”是:指 JDBC 的事务,上一次事务和本次事务之间是没有其他事务的,在执行一条命令(默认行为自动提交)都会产生一个事务,如果把 autocommit 设置为 false,需要主动 commit 才完成一个事务。
- 所谓的“逻辑事务”是:Spring 对 JDBC 的一个抽象,例如 Spring 默认的事务传播行为是 REQUIRED,当执行 @Transactional 注解标注的方法时,如果此时正处于一个事务(物理事务)中,那么加入到这个事务中,你可以理解为创建了一个“逻辑事务”,进行提交的时候不会执行 Connection 的 commit 方法,而是在外面的“物理事务”中进行 commit 时一并完成本次事务。
以下是一份关于 Spring事务 的高频面试题总结,涵盖核心概念、使用场景及常见问题:
一、基础概念
Spring事务的作用是什么?
- 确保一组数据库操作要么全部成功(提交),要么全部失败(回滚),保障数据一致性与完整性。
Spring事务管理的两种方式?
- 编程式事务:通过
TransactionTemplate
或PlatformTransactionManager
手动管理事务。 - 声明式事务:通过
@Transactional
注解或 XML 配置自动管理事务(推荐)。
- 编程式事务:通过
Spring事务的ACID特性如何实现?
- 依赖底层数据库的ACID支持,Spring通过事务管理器协调资源(如数据库连接)实现事务控制。
二、事务传播行为(Propagation)
- 列举常见的事务传播行为并解释
REQUIRED
(默认):当前有事务则加入,无则新建。REQUIRES_NEW
:新建事务,挂起当前事务(独立提交/回滚)。SUPPORTS
:有事务则加入,无则以非事务执行。NOT_SUPPORTED
:以非事务执行,挂起当前事务。MANDATORY
:必须在事务中调用,否则抛异常。NEVER
:必须在非事务中调用,否则抛异常。NESTED
:嵌套事务(依赖保存点机制,如JDBC 3.0)。
REQUIRED
vsREQUIRES_NEW
的区别?REQUIRED
是同一事务,回滚会影响外层;REQUIRES_NEW
是独立事务,回滚不影响外层。
三、事务隔离级别(Isolation)
- Spring支持的隔离级别有哪些?
DEFAULT
(数据库默认)READ_UNCOMMITTED
(可能脏读)READ_COMMITTED
(避免脏读)REPEATABLE_READ
(避免不可重复读)SERIALIZABLE
(完全串行化)
- 脏读、不可重复读、幻读的区别?
- 脏读:读到未提交的数据。
- 不可重复读:同一事务内多次读取同一数据结果不同(针对更新)。
- 幻读:同一事务内多次查询结果集不同(针对插入/删除)。
四、事务管理方式
PlatformTransactionManager
的核心实现类有哪些?DataSourceTransactionManager
(JDBC、MyBatis)HibernateTransactionManager
JpaTransactionManager
JtaTransactionManager
(分布式事务)
- 声明式事务的底层原理?
- 基于AOP(动态代理),通过拦截
@Transactional
注解方法,在方法前后开启/提交/回滚事务。
- 基于AOP(动态代理),通过拦截
五、@Transactional注解
@Transactional
注解的常用属性?propagation
、isolation
、timeout
、readOnly
、rollbackFor
/noRollbackFor
。
- 默认回滚的异常类型是什么?
- 默认回滚 RuntimeException 和 Error,受检异常(Checked Exception)不触发回滚。需通过
rollbackFor
指定。
- 默认回滚 RuntimeException 和 Error,受检异常(Checked Exception)不触发回滚。需通过
@Transactional
注解在类和方法上的优先级?- 方法上的注解优先级高于类上的注解。
六、事务失效场景
- 哪些情况会导致事务失效?
- 方法非
public
(CGLIB代理要求)。 - 自调用(未通过代理对象调用,如
this.method()
)。 - 异常被捕获未抛出或抛出的异常类型不匹配
rollbackFor
。 - 数据库引擎不支持事务(如MyISAM)。
- 未启用事务管理(如缺少
@EnableTransactionManagement
)。
- 方法非
七、高级问题
- Spring事务与分布式事务(JTA)的关系?
- Spring支持JTA管理多资源(如多个数据库、消息队列),需配合Atomikos等事务管理器。
- 嵌套事务(NESTED)如何实现?
- 基于保存点(Savepoint),子事务回滚不影响外层事务,但外层回滚会连带子事务。
- 如何监控事务的提交和回滚?
- 通过
TransactionSynchronizationManager
注册回调方法(如afterCommit
)。
- 通过
八、实战场景题
- 方法A调用方法B,B抛异常,如何让A和B一起回滚?
- B的事务传播行为设为
REQUIRED
(默认),异常向上传播,A和B同一事务。
- B的事务传播行为设为
- 如何实现事务中的异步操作?
- 异步方法需开启新事务(如
@Async
+@Transactional(propagation=REQUIRES_NEW)
),注意线程隔离问题。
- 异步方法需开启新事务(如
Spring 事务的传播级别
- REQUIRED:默认传播级别,如果正处于一个事务中,则加入;否则,创建一个事务
- SUPPORTS:如果正处于一个事务中,则加入;否则,不使用事务
- MANDATORY:如果当前正处于一个事务中,则加入;否则,抛出异常
- REQUIRES_NEW:无论如何都会创建一个新的事务,如果正处于一个事务中,会先挂起,然后创建
- NOT_SUPPORTED:不使用事务,如果正处于一个事务中,则挂起,不使用事务
- NEVER:不使用事务,如果正处于一个事务中,则抛出异常
- NESTED:嵌套事务,如果正处于一个事务中,则创建一个事务嵌套在其中(MySQL 采用 SAVEPOINT 保护点实现的);否则,创建一个事务
Spring 事务的使用示例
SpringMVC下
引入 Spring 事务相关依赖后,在 Spring MVC 中有两种(XML 配置和注解)驱动 Spring 事务的方式,如下面所示:
方式一
1 |
|
方式二
需要在 Spring 能扫描的一个 Bean 上添加一个 @EnableTransactionManagement 注解,然后添加一个 TransactionManagementConfigurer 实现类,如下:
1 |
|
此时你可以使用 @Transactional 注解标注在方法(或者类)上面,使得方法的执行处于一个事务中,如下:
1 |
|
SpringBoot下
在 Spring Boot 中我们使用 @Transactional 注解的时候好像不需要 @EnableTransactionManagement 注解驱动 Spring 事务模块,这是为什么?
和 Spring AOP 的 @EnableAspectJAutoProxy 注解类似,会有一个 TransactionAutoConfiguration 事务自动配置类,我们一起来看看:
1 |
|
只要存在 PlatformTransactionManager 这个 Class 对象就会将这个 Bean 注册到 IoC 容器中,里面涉及到一些 @Conditional 注解,这里就不一一解释了。
可以看到其中会有 @EnableTransactionManagement 注解,是不是和在 Spring MVC 中以注解驱动 Spring 事务的方式一样,但是好像没有 PlatformTransactionManager 事务管理器。
别急,我们看到这个自动配置类上面会有 @AutoConfigureAfter({DataSourceTransactionManagerAutoConfiguration.class}) 注解,表示会先加载 DataSourceTransactionManagerAutoConfiguration
这个自动配置类,我们一起来看看:
1 |
|
可以看到会注入一个 DataSourceTransactionManager 事务管理器,关联这个当前 DataSource 数据源对象
通过上面的使用示例我们可以注意到 @EnableTransactionManagement 注解可以驱动整个 Spring 事务模块
核心API
先简单介绍一下涉及到的一些主要的 API,对 Spring 事务的源码有一个印象,如下:
- Spring 事务 @Enable 模块驱动 - @EnableTransactionManagement
- Spring 事务注解 - @Transactional
- Spring 事务事件监听器 - @TransactionalEventListener
- Spring 事务定义 - TransactionDefinition
- Spring 事务状态 - TransactionStatus
- Spring 平台事务管理器 - PlatformTransactionManager
- Spring 事务代理配置 - ProxyTransactionManagementConfiguration
- Spring 事务 PointAdvisor 实现 - BeanFactoryTransactionAttributeSourceAdvisor
- Spring 事务 MethodInterceptor 实现 - TransactionInterceptor
- Spring 事务属性源 - TransactionAttributeSource
简单的介绍Spring事务:
- 需要通过
@EnableTransactionManagement
注解驱动整个 Spring 事务模块 - 可以通过
@Transactional
注解定义在某个类或者方法上面定义一个事务(传播性、隔离性等),开启事务 - ProxyTransactionManagementConfiguration 代理配置类用来定义一个 BeanFactoryTransactionAttributeSourceAdvisor 切面,是一个用于 Spring 事务的 AOP 切面
- Spring 事务底层就是通过 Spring AOP 实现的,可以在上面看到有一个 PointcutAdvisor 切面,关联的 Pointcut 内部有一个 TransactionAttributeSource 对象,会借助于 TransactionAnnotationParser 解析器解析 @Transactional 注解,将这个事务定义的一些属性封装成一个 TransactionDefinition 事务定义对象
- Spring AOP 拦截处理在 TransactionInterceptor 事务拦截器中,先借助 PlatformTransactionManager 平台事务管理器创建 TransactionStatus 事务对象,里面包含了 Transaction 事务,将 autocommit 自动提交关闭,方法的执行也就处于一个事务中
- 事务的相关属性会保存在许多 ThreadLocal 中,例如 DataSource、Connection 和 SqlSession 等属性,交由一个 TransactionSynchronizationManager 事务同步管理器进行管理,所以说 Spring 事务仅支持在一个线程中完成
@EnableTransactionManagement 注解驱动
1 |
|
可以看到默认情况下会注册两个 Bean
AutoProxyRegistrar
:注册一个 InfrastructureAdvisorAutoProxyCreator 对象,目的是创建代理对象,在讲解 Spring AOP 的时候讲述过,这里不再赘述ProxyTransactionManagementConfiguration
:一个 Spring 务代理配置类
ProxyTransactionManagementConfiguration
ProxyTransactionManagementConfiguration,Spring 事务代理配置类,定义好一个 AOP 切面
1 |
|
可以看到会注册三个 Bean:
- BeanFactoryTransactionAttributeSourceAdvisor 切面,这个 PointcutAdvisor 对象关联的 Pointcut 切点用于筛选 @Transactional 注解的方法(标注在类上也可以),在关联的 Advice 中会进行事务的拦截处理
- Advice 通知,就是一个 TransactionInterceptor 方法拦截器,关联着一个 AnnotationTransactionAttributeSource 对象
- AnnotationTransactionAttributeSource 事务属性资源对象,被 Pointcut 和 Advice 关联,用于解析 @Transactional 注解,在它的构造方法中会添加一个 SpringTransactionAnnotationParser 事务注解解析器,用于解析 @Transactional 注解,如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21// AnnotationTransactionAttributeSource.java
public AnnotationTransactionAttributeSource() {
this(true);
}
public AnnotationTransactionAttributeSource(boolean publicMethodsOnly) {
this.publicMethodsOnly = publicMethodsOnly;
if (jta12Present || ejb3Present) {
this.annotationParsers = new LinkedHashSet<>(4);
this.annotationParsers.add(new SpringTransactionAnnotationParser());
if (jta12Present) {
this.annotationParsers.add(new JtaTransactionAnnotationParser());
}
if (ejb3Present) {
this.annotationParsers.add(new Ejb3TransactionAnnotationParser());
}
}
else {
this.annotationParsers = Collections.singleton(new SpringTransactionAnnotationParser());
}
}
PointcutAdvisor 事务切面
org.springframework.transaction.interceptor.BeanFactoryTransactionAttributeSourceAdvisor,Spring 事务切面,如下:
1 | public class BeanFactoryTransactionAttributeSourceAdvisor extends AbstractBeanFactoryPointcutAdvisor { |
这里设置的 TransactionAttributeSource 就是上面的 AnnotationTransactionAttributeSource 对象,关联的 Pointcut 切点就是一个 TransactionAttributeSourcePointcut 对象
也就是说通过 Pointcut 事务切点筛选出来的 Bean 会创建一个代理对象,方法的拦截处理则交由 Advice 完成
Pointcut 事务切点
org.springframework.transaction.interceptor.TransactionAttributeSourcePointcut,Spring 事务的 AOP 切点,如下:
1 | abstract class TransactionAttributeSourcePointcut extends StaticMethodMatcherPointcut implements Serializable { |
判断这个方法是否需要被 TransactionInterceptor 事务拦截器进行拦截的过程如下:
- 目标类是 Spring 内部的事务相关类,则跳过,不需要创建代理对象
- 获取
AnnotationTransactionAttributeSource
对象 - 解析该方法相应的
@Transactional
注解,并将元信息封装成一个TransactionAttribute
对象,且缓存至AnnotationTransactionAttributeSource
对象中 - 如果有对应的 TransactionAttribute 对象,则表示匹配,需要进行事务的拦截处理
第 3 步解析 @Transactional 注解通过 AnnotationTransactionAttributeSource#getTransactionAttribute(..) 方法完成的,我们一起来看看这个解析过程
AnnotationTransactionAttributeSource#getTransactionAttribute
1 | // AbstractFallbackTransactionAttributeSource.java |
解析的过程如下:
- Object 内定义的方法跳过
- 获取缓存 Key,MethodClassKey 对象,关联 Method 和 Class 对象
- 尝试从缓存中获取该方法对应的 TransactionAttribute 对象,如果有的话
- 如果缓存中缓存的是一个“空”的 TransactionAttribute 对象,表示没有相应的 @Transactional 注解,返回 null
- 否则,返回缓存的 TransactionAttribute 对象
- 否则,开始解析方法对应的 @Transactional 注解
- 解析该方法或者类上面的 @Transactional 注解,封装成 RuleBasedTransactionAttribute 对象,优先从方法上面解析该注解,其次从类上解析该注解,没有的话返回的是 null
- 如果是 null,则缓存一个“空”的 TransactionAttribute 对象
- 否则,将该 TransactionAttribute 对象缓存
- 返回这个 TransactionAttribute 对象
注意,这里解析出来的 TransactionAttribute 会进行缓存,后续在 TransactionInterceptor(Advice)中无需解析,直接取缓存即可
小节
在这个 PointcutAdvisor 切面关联着一个 Pointcut 切点,为 TransactionAttributeSourcePointcut 对象,内部有一个 AnnotationTransactionAttributeSource 事务属性资源对象。
在这个切点判断某个方法是否需要进行事务处理时,通过内部的 AnnotationTransactionAttributeSource 对象解析 @Transactional 注解(没有的话表示不匹配),解析过程需要借助于 SpringTransactionAnnotationParser 解析器解析 @Transactional 注解,将这个事务定义的一些属性封装成一个 RuleBasedTransactionAttribute 事务定义对象(实现了 TransactionDefinition 接口),并缓存
TransactionInterceptor 事务拦截处理
通过 Pointcut 事务切点筛选出来的 Bean,会创建一个代理对象,Bean 内部肯定定义了 @Transactional 注解,如果是类上定义的 @Transactional 注解,每个方法都需要进行事务处理。
代理对象的事务拦截处理在 TransactionInterceptor 拦截器中,实现了 MethodInterceptor 方法拦截器,也就是实现了 Object invoke(MethodInvocation invocation) 这个方法
关键代码:
1 | // TransactionInterceptor.java |
调用的invokeWithinTransaction如下:
1 | // TransactionAspectSupport.java |
整个过程有点复杂,我们一步一步来看:
- 获取 @Transactional 注解对应的 TransactionAttribute 对象(如果在 AnnotationTransactionAttributeSource 解析过则取缓存),在 Pointcut 事务切点中已经分析过
- 获取
PlatformTransactionManager
事务管理器,需要指定,在 Spring Boot 中默认为DataSourceTransactionManager
- 获取方法的唯一标识,默认都是 类名.方法名
- 如果已有
@Transactional
注解对应的TransactionAttribute
对象,或者不是一个回调偏向的事务管理器(默认不是)- 调用
createTransactionIfNecessary
(..) 方法,创建TransactionInfo
事务信息对象(包含一个DefaultTransactionStatus
事务状态对象),绑定在 ThreadLocal 中 - 继续执行方法调用器(执行方法)
- 如果捕获到异常,则在这里完成事务,进行回滚或者提交,调用
completeTransactionAfterThrowing
(..) 方法 - finally 语句块,释放
ThreadLocal
中的TransactionInfo
对象,设置为上一个事务信息对象(没有的话为空) - 正常情况,到这里完成事务,调用
commitTransactionAfterReturning
(..) 方法 - 返回执行结果
- 调用
- 否则,就是支持回调的事务管理器,编程式事务(回调偏向),暂时忽略
我们继续来剖析一下上面的第4步的执行细节:
- 为本地方法的执行创建一个事务,过程比较复杂,可以先理解为需要把 Connection 连接的 autocommit 关闭,然后根据
@Transactional
注解的属性进行相关设置,例如根据事务的传播级别判断是否需要创建一个新的事务 - 事务准备好了,那么继续执行方法调用器,也就是方法的执行
- 捕获到异常,进行回滚,或者提交(异常类型不匹配)
- 正常情况,走到这里就完成事务,调用 Connection 的 commit() 方法完成本次事务(不是一定会执行,因为可能是“嵌套事务”或者“逻辑事务”等情况)
Spring创建事务过程
创建事务
createTransactionIfNecessary(..) 方法,创建一个事务(如果有必要的话),如下:
1 | // TransactionAspectSupport.java |
过程如下:
- 如果没有设置事务名称,则封装成一个 DelegatingTransactionAttribute 委托对象,支持返回一个事务名称(类名.方法名)
- 获取一个 TransactionStatus 对象(对事务的封装)
- 如果存在事务管理器,Spring Boot 中默认为 DataSourceTransactionManager,则通过事务管理器根据 @Transactional 注解获取一个 TransactionStatus 事务状态对象,该对象是对事务的封装,包含了以下信息:
- TransactionDefinition 事务定义
- DataSourceTransactionObject 数据源事务对象(包括 DataSource 和 Connection)
- 是否是一个新的事务
- 是否是一个新的事务同步器
- 被挂起的事务资源对象(如果有)
- 否则,跳过
- 创建一个 TransactionInfo 事务信息对象,并绑定到 ThreadLocal 中,如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22// TransactionAspectSupport.java
protected TransactionInfo prepareTransactionInfo( PlatformTransactionManager tm,
TransactionAttribute txAttr, String joinpointIdentification,
{ TransactionStatus status)
// <1> 创建一个 TransactionInfo 对象
TransactionInfo txInfo = new TransactionInfo(tm, txAttr, joinpointIdentification);
// <2> 如果有 @Transactional 注解的元信息
if (txAttr != null) {
// 设置 DefaultTransactionStatus 事务状态对象
txInfo.newTransactionStatus(status);
}
else { }
// We always bind the TransactionInfo to the thread, even if we didn't create
// a new transaction here. This guarantees that the TransactionInfo stack
// will be managed correctly even if no transaction was created by this aspect.
// <3> 将当前 TransactionInfo 对象保存至 ThreadLocal 中
txInfo.bindToThread();
// <4> 返回这个 TransactionInfo 对象
return txInfo;
}
可以看到,即使没有创建事务,也会创建一个 TransactionInfo 对象,并绑定到 ThreadLocal 中
PlatformTransactionManager管理事务
这个借口里面就是定义了三个方法
1 | public interface PlatformTransactionManager { |
三个方法分别对应创建事务,提交,回滚三个操作,关于 Spring 事务也就这三个核心步骤了
我们一个个看看
getTransaction方法
1 | // AbstractPlatformTransactionManager.java |
doBegin 方法
1 | // AbstractPlatformTransactionManager.java |
小结
Spring 创建事务的过程主要分为两种情况,当前线程不处于一个事务中和正处于一个事务中,两种情况都需要根据事务的传播级别来做出不同的处理。
创建一个事务的核心就是调用 Connection#setAutocommit(false) 方法,将自动提交关闭,这样一来,就可以在一个事务中根据行为作出相应的操作,例如出现异常进行回滚,没有问题则进行提交
- 当前线程不处于一个事务中:
- 如果是MANDATORY传播级别,则抛出异常
- 否则,如果是 REQUIRED | REQUIRES_NEW | NESTED 传播级别,则“创建”一个事务,将数据库的 commit 设置为 false,此时会设置事务状态里面的 newTransaction 属性 true,表示是一个新的事务
- 否则,创建一个“空”的事务状态对象,也就是不使用事务
- 当前线程正处于一个事务中:
- 如果是NEVER传播级别,则抛出异常
- 否则,如果是NOT_SUPPORTED传播级别,则将当前事务挂起,然后创建一个“空”的事务状态对象,也就是不使用事务
- 否则,如果是REQUIRES_NEW 传播级别,则将当前事务挂起,然后“创建”一个事务,将数据库的 commit 设置为 false,此时会设置事务状态里面的 newTransaction 属性 true,表示是一个新的事务;同时还保存了被挂起的事务相关资源,在本次事务结束后会唤醒它
- 否则,如果是 NESTED **传播级别,则沿用当前事务,就是设置事务状态里面的 newTransaction 属性 false,表示不是一个新的事务,不过会调用 Connection#setSavepoint(String) 方法创建一个 **SAVEPOINT 保存点,相当于嵌套事务
- 否则,就是 SUPPORTS | REQUIRED 传播级别,沿用当前事务,就是设置事务状态里面的 newTransaction 属性 false,表示不是一个新的事务
注意到 DefaultTransactionStatus 事务状态对象有一个 newTransaction
属性,通过它可以知道是否是一个新的事务,在后续的 commit 和 rollback 有着关键的作用
Spring提交事务
在 TransactionInterceptor 事务拦截处理过程中,如果方法的执行过程没有抛出异常,那么此时我们是不是需要调用 Connection#commit() 方法,提交本次事务
1 | // TransactionAspectSupport.java |
commit 方法
1 | // AbstractPlatformTransactionManager.java |
processCommit 方法
1 | // AbstractPlatformTransactionManager.java |
提交过程如下:
- 进行三个前置操作
- 准备工作,在 Spring 中为空方法
- 调用 TransactionSynchronization#beforeCommit 方法,例如在 Mybatis-Spring 中的 SqlSessionSynchronization 会调用其 SqlSession#commit() 方法,提交批量操作,刷新缓存
- 调用 TransactionSynchronization#beforeCompletion 方法,由 Spring 事务托管,不是真的关闭连接,从 ThreadLocal 中删除 DataSource 和 ConnectionHolder 的映射关系;例如在 Mybatis-Spring 中的 SqlSessionSynchronization 中,会从 ThreadLocal 中删除 SqlSessionFactory 和 SqlSessionHolder 的映射关系,且调用其 SqlSession#close() 方法
- 标记三个前置操作已完成
- 如果有保存点,即嵌套事务,则调用 Connection#releaseSavepoint(Savepoint) 方法释放保存点,等外层的事务进行提交
- 否则,如果是一个新的事务,根据之前一直提到的 newTransaction 属性进行判断是否是一个新的事务
- 提交事务,执行 Connection#commit() 方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16// DataSourceTransactionManager.java
protected void doCommit(DefaultTransactionStatus status) {
DataSourceTransactionObject txObject = (DataSourceTransactionObject) status.getTransaction();
Connection con = txObject.getConnectionHolder().getConnection();
if (status.isDebug()) {
logger.debug("Committing JDBC transaction on Connection [" + con + "]");
}
try {
con.commit();
}
catch (SQLException ex) {
throw new TransactionSystemException("Could not commit JDBC transaction", ex);
}
}
- 提交事务,执行 Connection#commit() 方法
- 否则,在事务被标记为全局回滚的情况下是否提前失败(默认为 false)
- 触发提交后的回调,调用 TransactionSynchronization#afterCommit 方法,JMS 会有相关操作,暂时忽略
- 触发完成后的回调,事务同步状态为已提交,调用 TransactionSynchronization#afterCompletion 方法,例如在 Mybatis-Spring 中的 SqlSessionSynchronization 中,会从 ThreadLocal 中删除 SqlSessionFactory 和 SqlSessionHolder 的映射关系,且调用其 SqlSession#close() 方法,解决可能出现的跨线程的情况
- 在完成后清理,清理相关资源,“释放”连接,唤醒被挂起的资源,如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23// AbstractPlatformTransactionManager.java
private void cleanupAfterCompletion(DefaultTransactionStatus status) {
// <1> 设置为已完成
status.setCompleted();
// <2> 如果是一个新的事务同步器
if (status.isNewSynchronization()) {
// 清理事务管理器中的 ThreadLocal 相关资源,包括事务同步器、事务名称、只读属性、隔离级别、真实的事务激活状态
TransactionSynchronizationManager.clear();
}
// <3> 如果是一个新的事务
if (status.isNewTransaction()) {
// 清理 Connection 资源,例如释放 Connection 连接,将其引用计数减一(不会真的关闭)
// 如果这个 `con` 是中途创建的,和 ThreadLocal 中的不一致,则需要关闭
doCleanupAfterCompletion(status.getTransaction());
}
// <4> 如果之前有被挂起的事务,则唤醒
if (status.getSuspendedResources() != null) {
Object transaction = (status.hasTransaction() ? status.getTransaction() : null);
// 唤醒被挂起的事务和资源,重新将 DataSource 和 ConnectionHolder 的映射绑定到 ThreadLocal 中
// 将之前挂起的相关属性重新设置到 ThreadLocal 中
resume(transaction, (SuspendedResourcesHolder) status.getSuspendedResources());
}
}
整个过程在提交事务的前后会进行相关处理,例如清理资源;对于嵌套事务,这里会释放保存点,等外层的事务进行提交;对于新的事务,这里会调用Connection#commit()方法提交事务;其他情况不会真的提交事务,在这里仅清理相关资源,唤醒被挂起的资源
Spring回滚事务
在 TransactionInterceptor 事务拦截处理过程中,如果方法的执行过程抛出异常,那么此时我们是不是需要调用 Connection#roback() 方法,对本次事务进行回滚
1 | // TransactionAspectSupport.java |
回滚的过程如下:
- 调用 TransactionSynchronization#beforeCompletion 方法,由 Spring 事务托管,不是真的关闭连接,从 ThreadLocal 中删除 DataSource 和 ConnectionHolder 的映射关系;例如在 Mybatis-Spring 中的 SqlSessionSynchronization 中,会从 ThreadLocal 中删除 SqlSessionFactory 和 SqlSessionHolder 的映射关系,且调用其 SqlSession#close() 方法
- 如果有 Savepoint 保存点,也就是嵌套事务,则回滚到保存点,调用 Connection#rollback(Savepoint) 方法回滚到保存点,再调用Connection#releaseSavepoint(Savepoint) 方法释放该保存点
- 否则,如果是新的事务,例如传播级别为 REQUIRED_NEW 则一定是一个新的事务,则对当前事务进行回滚,调用 Connection#rollback() 方法,如下:
1
2
3
4
5
6
7
8
9
10
11
12
13// DataSourceTransactionManager.java
protected void doRollback(DefaultTransactionStatus status) {
DataSourceTransactionObject txObject = (DataSourceTransactionObject) status.getTransaction();
Connection con = txObject.getConnectionHolder().getConnection();
try {
con.rollback();
}
catch (SQLException ex) {
throw new TransactionSystemException("Could not roll back JDBC transaction", ex);
}
} - 否则,不是新的事务也没有保存点,那就是加入到一个已有的事务这种情况,例如 REQUIRED 传播级别,如果已存在一个事务,则加入其中
- 如果已经标记为回滚,或当加入事务失败时全局回滚(默认 true),那么设置 ConnectionHolder 的 rollbackOnly 为 true,也就是标记需要全局回滚,对应到前面“提交事务”的时候会判断是否标记为全局回滚,标记了则进行回滚,而不是提交
可以看到,对于默认的REQUIRED事务传播级别,如果已有一个事务(“物理事务”),则加入到当前事务中(相当于创建了一个“逻辑事务”),当这个“逻辑事务”出现异常时,整个事务(包括外面的“物理事务”)都需要回滚
总结
本文对 Spring 事务做了比较详细的讲解,我们通过 @EnableTransactionManagement 注解驱动整个 Spring 事务模块,此时会往 IoC 注入一个 PointcutAdvisor 事务切面,关联了一个 TransactionAttributeSourcePointcut(Pointcut)事务切点和一个 TransactionInterceptor(Advice)事务拦截器。
这个 TransactionAttributeSourcePointcut(Pointcut)事务切点,它里面关联了一个 AnnotationTransactionAttributeSource 事务属性资源对象,通过它解析这个方法(或者类)上面的 @Transactional 注解;
底层需要借助 SpringTransactionAnnotationParser 进行解析,解析出一个 TransactionAttribute 事务属性对象,并缓存;
没有解析出对应的 TransactionAttribute 对象也就不会被事务拦截器拦截,否则,需要为这个 Bean 创建一个代理对象。
这个 TransactionInterceptor(Advice)事务拦截器让方法的执行处于一个事务中(如果定义了 @Transactional 注解,且被 public 修饰符修饰);
首先会创建一个事务(如果有必要),最核心就是将数据库的 commit 设置为 false,不自动提交,在方法执行完后进行提交(或者回滚);
拓展
Spirng 事务(Transactions)的底层实现总体上是这样的:以 @EnableXxx 模块驱动注解驱动整个模块,同时会注入一个 PointcutAdvisor 切面,关联一个 Pointcut 和一个 Advice 通知;通过 Pointcut 筛选出符合条件的 Bean;然后在 Advice 中进行拦截处理,实现相应的功能。
Spring 缓存(Caching)的底层实现和 Spirng 事务(Transactions)整体上差不多,当你对 Spirng 事务(Transactions)的底层了解后,你会发现 Spring 缓存(Caching)的实现基本是照搬过来的。
Spring 异步处理(Async)的底层实现和上面两者类似(原理差不多),不过它没有直接注入一个 PointcutAdvisor 切面,而是注入了一个 AbstractAdvisingBeanPostProcessor
对象,继承 ProxyProcessorSupport(AOP 代理配置类),且实现 BeanPostProcessor 接口;在这个对象里面会关联一个 AsyncAnnotationAdvisor 切面对象,然后通过实现 BeanPostProcessor 接口在 Spring Bean 的生命周期中的初始化后进行扩展,对于符合条件的 Bean 会通过 ProxyFactory 创建一个代理对象;AsyncAnnotationAdvisor 关联的 Advice 会对方法进行拦截处理,也就是将方法的执行放入一个 Executor 线程池中执行,会返回一个 Future 可用于获取执行结果。
几种会导致Spring事务失效的场景
在Spring中,事务失效可能由以下场景引起:
- 非public方法
Spring事务默认只对public方法生效,非public方法上的@Transactional
注解会被忽略。 - 自调用
类内部方法调用另一个带有@Transactional
注解的方法时,事务不会生效,因为代理无法拦截自调用。 - 异常处理不当
默认情况下,Spring只对未检查异常(RuntimeException)回滚。如果捕获了异常且未重新抛出,或抛出了检查异常,事务不会回滚。 - 事务传播配置错误
如果事务传播行为配置为Propagation.NOT_SUPPORTED
或Propagation.NEVER
,事务将不会生效。 - 多线程调用
新线程中的操作不在原事务管理范围内,导致事务失效。 - 数据库引擎不支持事务
如MySQL的MyISAM引擎不支持事务,即使使用@Transactional
注解也不会生效。 - 未启用事务管理
如果未在配置中启用事务管理(如未配置@EnableTransactionManagement
),事务注解不会生效。 - 错误的代理模式
如果使用基于类的代理(CGLIB)且类为final,或使用基于接口的代理(JDK动态代理)但方法未在接口中声明,事务可能失效。 - 手动回滚未处理
如果在代码中手动调用了setRollbackOnly()
但未正确处理,事务可能不会按预期回滚。 - 事务管理器配置错误
如未正确配置事务管理器,或使用了错误的事务管理器,事务将无法生效。