死磕Spring之AOP篇 - Spring 事务详解
该系列文章是本人在学习 Spring 的过程中总结下来的,里面涉及到相关源码,可能对读者不太友好,请结合我的源码注释 Spring 源码分析 GitHub 地址 进行阅读。
Spring 版本:5.1.14.RELEASE
在开始阅读 Spring AOP 源码之前,需要对 Spring IoC 有一定的了解,可查看我的 《死磕Spring之IoC篇 - 文章导读》 这一系列文章
了解 AOP 相关术语,可先查看 《Spring AOP 常见面试题) 》 这篇文章
该系列其他文章请查看:《死磕 Spring 之 AOP 篇 - 文章导读》
前序
前面我的一系列文章对 Spring AOP 进行了比较详细的讲述,相信你对 Spring AOP 有了更加深入的理解,如果你对于 Spring AOP 不是很了解,建议先查看我前面的一系列文章对 Spring AOP 有一定的了解,因为 Spring 事务是借助于 Spring AOP 实现的。由于这段时间有点忙(太懒了~),没能及时更新 Spring AOP 在 Spring 内部的应用相关内容,趁着还对 Spring AOP 有一点印象,我们一起来看看 Spring 事务的相关源码。我猜应该是比较容易理解的,因为已经有了 Spring AOP 的基础,相信对于 Spring 事务会“轻而易举”地掌握,嘻嘻~
我们先了解一下 Spring 事务里面的“物理事务”和“逻辑事务”,所谓的“物理事务”是指 JDBC 的事务,上一次事务和本次事务之间是没有其他事务的,在执行一条命令(默认行为自动提交)都会产生一个事务,如果把 autocommit
设置为 false
,需要主动 commit
才完成一个事务。所谓的“逻辑事务”是 Spring 对 JDBC 的一个抽象,例如 Spring 默认的事务传播行为是 REQUIRED,当执行 @Transactional
注解标注的方法时,如果此时正处于一个事务(物理事务)中,那么加入到这个事务中,你可以理解为创建了一个“逻辑事务”,进行提交的时候不会执行 Connection 的 commit
方法,而是在外面的“物理事务”中进行 commit
时一并完成本次事务。
Spring 事务的传播级别
- REQUIRED:默认传播级别,如果正处于一个事务中,则加入;否则,创建一个事务
- SUPPORTS:如果正处于一个事务中,则加入;否则,不使用事务
- MANDATORY:如果当前正处于一个事务中,则加入;否则,抛出异常
- REQUIRES_NEW:无论如何都会创建一个新的事务,如果正处于一个事务中,会先挂起,然后创建
- NOT_SUPPORTED:不使用事务,如果正处于一个事务中,则挂起,不使用事务
- NEVER:不使用事务,如果正处于一个事务中,则抛出异常
- NESTED:嵌套事务,如果正处于一个事务中,则创建一个事务嵌套在其中(MySQL 采用 SAVEPOINT 保护点实现的);否则,创建一个事务
关于 Spring 事务传播级别更多的细节在接下来的源码中进行讨论
Spring 事务的使用示例
相信看到这篇文章的你对于 @Transactional
注解的使用肯定非常了解,不过这里还是列举以下它的使用方式
Spring MVC
引入 Spring 事务相关依赖后,在 Spring MVC 中有两种(XML 配置和注解)驱动 Spring 事务的方式,如下面所示:
方式一:
<?xml version="1.0" encoding="UTF-8"?>
<beans>
<!-- 定义一个数据源 -->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">...</bean>
<!-- 事务管理器 -->
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!-- 指定数据源 -->
<property name="dataSource" ref="dataSource"/>
</bean>
<!-- 事务模块驱动,指定使用上面定义事务管理器,默认值为 transactionManager -->
<tx:annotation-driven transaction-manager="txManager"/>
</beans>
方式二:
需要在 Spring 能扫描的一个 Bean 上添加一个 @EnableTransactionManagement
注解,然后添加一个 TransactionManagementConfigurer 实现类,如下:
package tk.mybatis.simple.controller;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.TransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import org.springframework.transaction.annotation.TransactionManagementConfigurer;
import javax.annotation.Resource;
import javax.sql.DataSource;
@Configuration
@EnableTransactionManagement
public class TransactionManagerConfig implements TransactionManagementConfigurer {
@Resource
DataSource dataSource;
@Override
public TransactionManager annotationDrivenTransactionManager() {
// 返回一个事务管理器,设置数据源
return new DataSourceTransactionManager(dataSource);
}
}
此时你可以使用 @Transactional
注解标注在方法(或者类)上面,使得方法的执行处于一个事务中,如下:
@Transactional(propagation = Propagation.REQUIRED, rollbackFor = {Exception.class})
public void update(){
try {
// 数据库操作
} catch (Exeception e){
// 将事务状态设置为回滚
TransactionInterceptor.currentTransactionStatus().setRollbackOnly();
}
}
Spring Boot
在 Spring Boot 中我们使用 @Transactional
注解的时候好像不需要 @EnableTransactionManagement
注解驱动 Spring 事务模块,这是为什么?和 Spring AOP 的 @EnableAspectJAutoProxy
注解类似,会有一个 TransactionAutoConfiguration 事务自动配置类,我们一起来看看:
@Configuration
@ConditionalOnClass(PlatformTransactionManager.class)
@AutoConfigureAfter({ JtaAutoConfiguration.class, HibernateJpaAutoConfiguration.class,
DataSourceTransactionManagerAutoConfiguration.class,
Neo4jDataAutoConfiguration.class })
@EnableConfigurationProperties(TransactionProperties.class)
public class TransactionAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public TransactionManagerCustomizers platformTransactionManagerCustomizers(
ObjectProvider<List<PlatformTransactionManagerCustomizer<?>>> customizers) {
return new TransactionManagerCustomizers(customizers.getIfAvailable());
}
@Configuration
@ConditionalOnSingleCandidate(PlatformTransactionManager.class)
public static class TransactionTemplateConfiguration {
private final PlatformTransactionManager transactionManager;
public TransactionTemplateConfiguration(
PlatformTransactionManager transactionManager) {
this.transactionManager = transactionManager;
}
@Bean
@ConditionalOnMissingBean
public TransactionTemplate transactionTemplate() {
return new TransactionTemplate(this.transactionManager);
}
}
@Configuration
@ConditionalOnBean(PlatformTransactionManager.class)
@ConditionalOnMissingBean(AbstractTransactionManagementConfiguration.class)
public static class EnableTransactionManagementConfiguration {
@Configuration
@EnableTransactionManagement(proxyTargetClass = false)
@ConditionalOnProperty(prefix = "spring.aop", name = "proxy-target-class", havingValue = "false", matchIfMissing = false)
public static class JdkDynamicAutoProxyConfiguration { }
@Configuration
@EnableTransactionManagement(proxyTargetClass = true)
@ConditionalOnProperty(prefix = "spring.aop", name = "proxy-target-class", havingValue = "true", matchIfMissing = true)
public static class CglibAutoProxyConfiguration { }
}
}
是不是很熟悉,只要存在 PlatformTransactionManager 这个 Class 对象就会将这个 Bean 注册到 IoC 容器中,里面涉及到一些 @Conditional
注解,这里就不一一解释了。可以看到其中会有 @EnableTransactionManagement
注解,是不是和在 Spring MVC 中以注解驱动 Spring 事务的方式一样,但是好像没有 PlatformTransactionManager 事务管理器。别急,我们看到这个自动配置类上面会有 @AutoConfigureAfter({DataSourceTransactionManagerAutoConfiguration.class})
注解,表示会先加载 DataSourceTransactionManagerAutoConfiguration 这个自动配置类,我们一起来看看:
@Configuration
@ConditionalOnClass({ JdbcTemplate.class, PlatformTransactionManager.class })
@AutoConfigureOrder(Ordered.LOWEST_PRECEDENCE)
@EnableConfigurationProperties(DataSourceProperties.class)
public class DataSourceTransactionManagerAutoConfiguration {
@Configuration
@ConditionalOnSingleCandidate(DataSource.class)
static class DataSourceTransactionManagerConfiguration {
private final DataSource dataSource;
private final TransactionManagerCustomizers transactionManagerCustomizers;
DataSourceTransactionManagerConfiguration(DataSource dataSource,
ObjectProvider<TransactionManagerCustomizers> transactionManagerCustomizers) {
this.dataSource = dataSource;
this.transactionManagerCustomizers = transactionManagerCustomizers
.getIfAvailable();
}
@Bean
@ConditionalOnMissingBean(PlatformTransactionManager.class)
public DataSourceTransactionManager transactionManager(
DataSourceProperties properties) {
DataSourceTransactionManager transactionManager = new DataSourceTransactionManager(
this.dataSource);
if (this.transactionManagerCustomizers != null) {
this.transactionManagerCustomizers.customize(transactionManager);
}
return transactionManager;
}
}
}
可以看到会注入一个 DataSourceTransactionManager 事务管理器,关联这个当前 DataSource 数据源对象
好了,通过上面的使用示例我们可以注意到 @EnableTransactionManagement
注解可以驱动整个 Spring 事务模块,当然,<annotation-driven />
标签的原理和注解差不多,前面也讲述了非常多 Spring 自定义标签的实现原理,这里我们就不分析了,那么我们一起来看看 @EnableTransactionManagement
这个注解
核心 API
在开始查看 Spring 事务的源码之前,我想有必要先简单介绍一下涉及到的一些主要的 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 事务仅支持在一个线程中完成
Spring 事务非常复杂,接下来我们逐步分析
@EnableTransactionManagement 注解驱动
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(TransactionManagementConfigurationSelector.class)
public @interface EnableTransactionManagement {
/**
* 默认优先使用 JDK 动态代理
*/
boolean proxyTargetClass() default false;
/**
* 默认使用 Spring AOP 代理模式
*/
AdviceMode mode() default AdviceMode.PROXY;
int order() default Ordered.LOWEST_PRECEDENCE;
}
可以看到有一个 @Import
注解,它的值是一个 TransactionManagementConfigurationSelector 类,也就是说 Spring 事务的驱动入口在这里面,关于 @Import
注解的原理可查看我的 《死磕Spring之IoC篇 - @Bean 等注解的实现原理》 这篇文章
TransactionManagementConfigurationSelector
public class TransactionManagementConfigurationSelector extends AdviceModeImportSelector<EnableTransactionManagement> {
@Override
protected String[] selectImports(AdviceMode adviceMode) {
switch (adviceMode) {
// 默认使用代理模式
case PROXY:
return new String[]{
// 注册一个 InfrastructureAdvisorAutoProxyCreator 对象,目的是创建代理对象
AutoProxyRegistrar.class.getName(),
// 【关键】注册一个 Spring 事务代理配置类
ProxyTransactionManagementConfiguration.class.getName()};
// 选择 AspectJ 模式
case ASPECTJ:
return new String[]{determineTransactionAspectClass()};
default:
return null;
}
}
private String determineTransactionAspectClass() {
return (ClassUtils.isPresent("javax.transaction.Transactional", getClass().getClassLoader()) ?
TransactionManagementConfigUtils.JTA_TRANSACTION_ASPECT_CONFIGURATION_CLASS_NAME :
TransactionManagementConfigUtils.TRANSACTION_ASPECT_CONFIGURATION_CLASS_NAME);
}
}
可以看到默认情况下会注册两个 Bean
- AutoProxyRegistrar,注册一个 InfrastructureAdvisorAutoProxyCreator 对象,目的是创建代理对象,在讲解 Spring AOP 的时候讲述过,这里不再赘述
- ProxyTransactionManagementConfiguration,一个 Spring 务代理配置类
ProxyTransactionManagementConfiguration
org.springframework.transaction.annotation.ProxyTransactionManagementConfiguration
,Spring 事务代理配置类,定义好一个 AOP 切面
@Configuration
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public class ProxyTransactionManagementConfiguration extends AbstractTransactionManagementConfiguration {
@Bean(name = TransactionManagementConfigUtils.TRANSACTION_ADVISOR_BEAN_NAME)
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public BeanFactoryTransactionAttributeSourceAdvisor transactionAdvisor() {
// <1> 创建 PointcutAdvisor 对象,作为 @Transactional 注解的一个切面
BeanFactoryTransactionAttributeSourceAdvisor advisor = new BeanFactoryTransactionAttributeSourceAdvisor();
// <2> 【Pointcut】设置 AnnotationTransactionAttributeSource,被关联在 Pointcut 中
// 借助于 TransactionAnnotationParser 解析器解析 @Transactional 注解
advisor.setTransactionAttributeSource(transactionAttributeSource());
// <3> 【Advice】设置 Advice 为 TransactionInterceptor 事务拦截器
advisor.setAdvice(transactionInterceptor());
if (this.enableTx != null) {
advisor.setOrder(this.enableTx.<Integer>getNumber("order"));
}
return advisor;
}
@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public TransactionAttributeSource transactionAttributeSource() {
return new AnnotationTransactionAttributeSource();
}
@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public TransactionInterceptor transactionInterceptor() {
// 创建 TransactionInterceptor 事务拦截器(MethodInterceptor 对象)
TransactionInterceptor interceptor = new TransactionInterceptor();
// 设置这个 AnnotationTransactionAttributeSource 对象,@Bean 注解标注的方法返回的都是同一个对象
interceptor.setTransactionAttributeSource(transactionAttributeSource());
if (this.txManager != null) {
// 设置默认的事务管理器
interceptor.setTransactionManager(this.txManager);
}
return interceptor;
}
}
可以看到会注册三个 Bean:
BeanFactoryTransactionAttributeSourceAdvisor
切面,这个 PointcutAdvisor 对象关联的 Pointcut 切点用于筛选@Transactional
注解的方法(标注在类上也可以),在关联的 Advice 中会进行事务的拦截处理Advice 通知,就是一个
TransactionInterceptor
方法拦截器,关联着一个AnnotationTransactionAttributeSource
对象AnnotationTransactionAttributeSource
事务属性资源对象,被 Pointcut 和 Advice 关联,用于解析@Transactional
注解,在它的构造方法中会添加一个SpringTransactionAnnotationParser
事务注解解析器,用于解析@Transactional
注解,如下:// 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 事务切面,如下:
public class BeanFactoryTransactionAttributeSourceAdvisor extends AbstractBeanFactoryPointcutAdvisor {
/**
* 事务属性源对象,用于解析 @Transactional 注解
*/
@Nullable
private TransactionAttributeSource transactionAttributeSource;
/**
* Pointcut 对象,用于判断 JoinPoint 是否匹配
*/
private final TransactionAttributeSourcePointcut pointcut = new TransactionAttributeSourcePointcut() {
@Override
@Nullable
protected TransactionAttributeSource getTransactionAttributeSource() {
return transactionAttributeSource;
}
};
public void setTransactionAttributeSource(TransactionAttributeSource transactionAttributeSource) {
this.transactionAttributeSource = transactionAttributeSource;
}
public void setClassFilter(ClassFilter classFilter) {
this.pointcut.setClassFilter(classFilter);
}
@Override
public Pointcut getPointcut() {
return this.pointcut;
}
}
设置的 TransactionAttributeSource 就是上面的 AnnotationTransactionAttributeSource
对象,关联的 Pointcut 切点就是一个 TransactionAttributeSourcePointcut
对象
也就是说通过 Pointcut 事务切点筛选出来的 Bean 会创建一个代理对象,方法的拦截处理则交由 Advice 完成
Pointcut 事务切点
org.springframework.transaction.interceptor.TransactionAttributeSourcePointcut
,Spring 事务的 AOP 切点,如下:
abstract class TransactionAttributeSourcePointcut extends StaticMethodMatcherPointcut implements Serializable {
@Override
public boolean matches(Method method, Class<?> targetClass) {
// <1> 目标类是 Spring 内部的事务相关类,则跳过,不需要创建代理对象
if (TransactionalProxy.class.isAssignableFrom(targetClass) ||
PlatformTransactionManager.class.isAssignableFrom(targetClass) ||
PersistenceExceptionTranslator.class.isAssignableFrom(targetClass)) {
return false;
}
// <2 获取 AnnotationTransactionAttributeSource 对象
TransactionAttributeSource tas = getTransactionAttributeSource();
// <3> 解析该方法相应的 @Transactional 注解,并将元信息封装成一个 TransactionAttribute 对象
// 且缓存至 AnnotationTransactionAttributeSource 对象中
// <4> 如果有对应的 TransactionAttribute 对象,则表示匹配,需要进行事务的拦截处理
return (tas == null || tas.getTransactionAttribute(method, targetClass) != null);
}
}
判断这个方法是否需要被 TransactionInterceptor
事务拦截器进行拦截的过程如下:
- 目标类是 Spring 内部的事务相关类,则跳过,不需要创建代理对象
- 获取
AnnotationTransactionAttributeSource
对象 - 解析该方法相应的
@Transactional
注解,并将元信息封装成一个TransactionAttribute
对象,且缓存至AnnotationTransactionAttributeSource
对象中 - 如果有对应的
TransactionAttribute
对象,则表示匹配,需要进行事务的拦截处理
第 3
步解析 @Transactional
注解通过 AnnotationTransactionAttributeSource#getTransactionAttribute(..)
方法完成的,我们一起来看看这个解析过程
@Transactional 注解的解析
1. getTransactionAttribute 方法
// AbstractFallbackTransactionAttributeSource.java
@Override
@Nullable
public TransactionAttribute getTransactionAttribute(Method method, @Nullable Class<?> targetClass) {
// <1> java.lang.Object 内定义的方法跳过
if (method.getDeclaringClass() == Object.class) {
return null;
}
// First, see if we have a cached value.
// <2> 获取缓存 Key,MethodClassKey 对象,关联 Method 和 Class 对象
Object cacheKey = getCacheKey(method, targetClass);
// <3> 尝试从缓存中获取该方法对应的 TransactionAttribute 对象
TransactionAttribute cached = this.attributeCache.get(cacheKey);
if (cached != null) {
// Value will either be canonical value indicating there is no transaction attribute,
// or an actual transaction attribute.
// <3.1> 缓存中缓存的是一个空的 TransactionAttribute 对象
// 表示没有相应的 @Transactional 注解,返回 `null`
if (cached == NULL_TRANSACTION_ATTRIBUTE) {
return null;
}
// <3.2> 返回缓存的 TransactionAttribute 对象
else {
return cached;
}
}
// <4> 开始解析方法对应的 @Transactional 注解
else {
// We need to work it out.
// <4.1> 解析该方法或者类上面的 @Transactional 注解,封装成 RuleBasedTransactionAttribute 对象
// 优先从方法上面解析该注解,其次从类上解析该注解,没有的话返回的是 `null`
TransactionAttribute txAttr = computeTransactionAttribute(method, targetClass);
// Put it in the cache.
// <4.2> 如果是 `null`,则缓存一个空的 TransactionAttribute 对象
if (txAttr == null) {
this.attributeCache.put(cacheKey, NULL_TRANSACTION_ATTRIBUTE);
}
// <4.3> 否则,将该 TransactionAttribute 对象缓存
else {
String methodIdentification = ClassUtils.getQualifiedMethodName(method, targetClass);
if (txAttr instanceof DefaultTransactionAttribute) {
// 设置方法的描述符(类名.方法名)
((DefaultTransactionAttribute) txAttr).setDescriptor(methodIdentification);
}
this.attributeCache.put(cacheKey, txAttr);
}
// <4.4> 返回这个 TransactionAttribute 对象
return txAttr;
}
}
解析过程如下:
- Object 内定义的方法跳过
- 获取缓存 Key,MethodClassKey 对象,关联 Method 和 Class 对象
- 尝试从缓存中获取该方法对应的 TransactionAttribute 对象,如果有的话
- 如果缓存中缓存的是一个“空”的 TransactionAttribute 对象,表示没有相应的
@Transactional
注解,返回null
- 否则,返回缓存的 TransactionAttribute 对象
- 如果缓存中缓存的是一个“空”的 TransactionAttribute 对象,表示没有相应的
- 否则,开始解析方法对应的
@Transactional
注解- 解析该方法或者类上面的
@Transactional
注解,封装成RuleBasedTransactionAttribute
对象,优先从方法上面解析该注解,其次从类上解析该注解,没有的话返回的是null
- 如果是
null
,则缓存一个“空”的 TransactionAttribute 对象 - 否则,将该 TransactionAttribute 对象缓存
- 返回这个 TransactionAttribute 对象
- 解析该方法或者类上面的
注意,这里解析出来的 TransactionAttribute 会进行缓存,后续在 TransactionInterceptor
(Advice)中无需解析,直接取缓存即可
上面第 4.1
步调用 computeTransactionAttribute(..)
方法解析 @Transactional
注解,如下:
2. computeTransactionAttribute 方法
// AbstractFallbackTransactionAttributeSource.java
@Nullable
protected TransactionAttribute computeTransactionAttribute(Method method, @Nullable Class<?> targetClass) {
// Don't allow no-public methods as required.
// 如果不允许非 public 修饰的方法(默认允许),则判断是否为 public 修饰,不是的话返回 null
if (allowPublicMethodsOnly() && !Modifier.isPublic(method.getModifiers())) {
return null;
}
// The method may be on an interface, but we need attributes from the target class.
// If the target class is null, the method will be unchanged.
// 获取方法对象(而不是桥接方法)
Method specificMethod = AopUtils.getMostSpecificMethod(method, targetClass);
// First try is the method in the target class.
// 通过 SpringTransactionAnnotationParser 解析方法上面的 @Transactional 注解
// 并将注解的元信息封装到 RuleBasedTransactionAttribute 中,没有注解的话返回 null
TransactionAttribute txAttr = findTransactionAttribute(specificMethod);
if (txAttr != null) {
// 存在的话直接返回
return txAttr;
}
// Second try is the transaction attribute on the target class.
// 如果方法上面没有,则尝试解析类上面的 @Transactional 注解
txAttr = findTransactionAttribute(specificMethod.getDeclaringClass());
// 存在这个注解,且方法是用户级别(不是 Spring 内部合成的)
if (txAttr != null && ClassUtils.isUserLevelMethod(method)) {
return txAttr;
}
// 如果还没有找到 @Transactional 注解的元信息,则尝试从原 Method 对象中查找
if (specificMethod != method) {
// Fallback is to look at the original method.
// 处理过程如上
txAttr = findTransactionAttribute(method);
if (txAttr != null) {
return txAttr;
}
// Last fallback is the class of the original method.
// 处理过程如上
txAttr = findTransactionAttribute(method.getDeclaringClass());
if (txAttr != null && ClassUtils.isUserLevelMethod(method)) {
return txAttr;
}
}
return null;
}
可以看到,默认情况是只支持 public
修饰的方法,对于方法和类上面的 @Transactional
注解都是支持的,优先从方法上面解析,其次从所在类上面解析,处理过程都在 findTransactionAttribute(..)
方法中
3. findTransactionAttribute 方法
// AnnotationTransactionAttributeSource.java
@Override
protected TransactionAttribute findTransactionAttribute(Class<?> clazz) {
// 解析类上面的 @Transactional 注解,并将注解的元信息封装到 RuleBasedTransactionAttribute 中
return determineTransactionAttribute(clazz);
}
@Override
protected TransactionAttribute findTransactionAttribute(Method method) {
// 解析方法上面的 @Transactional 注解,并将注解的元信息封装到 RuleBasedTransactionAttribute 中
return determineTransactionAttribute(method);
}
protected TransactionAttribute determineTransactionAttribute(AnnotatedElement element) {
for (TransactionAnnotationParser annotationParser : this.annotationParsers) {
// 通过 SpringTransactionAnnotationParser 解析 @Transactional 注解
TransactionAttribute attr = annotationParser.parseTransactionAnnotation(element);
if (attr != null) {
return attr;
}
}
return null;
}
通过 SpringTransactionAnnotationParser
解析器进行方法或者类上面的 @Transactional
注解
4. SpringTransactionAnnotationParser
public class SpringTransactionAnnotationParser implements TransactionAnnotationParser, Serializable {
@Override
@Nullable
public TransactionAttribute parseTransactionAnnotation(AnnotatedElement element) {
// 找到这个方法的 @Transactional 注解
AnnotationAttributes attributes = AnnotatedElementUtils.findMergedAnnotationAttributes(
element, Transactional.class, false, false);
if (attributes != null) {
// 将 @Transactional 注解的元信息封装到 RuleBasedTransactionAttribute 对象中
return parseTransactionAnnotation(attributes);
}
else {
return null;
}
}
public TransactionAttribute parseTransactionAnnotation(Transactional ann) {
return parseTransactionAnnotation(AnnotationUtils.getAnnotationAttributes(ann, false, false));
}
protected TransactionAttribute parseTransactionAnnotation(AnnotationAttributes attributes) {
RuleBasedTransactionAttribute rbta = new RuleBasedTransactionAttribute();
// 事务传播性
Propagation propagation = attributes.getEnum("propagation");
rbta.setPropagationBehavior(propagation.value());
// 事务隔离级别
Isolation isolation = attributes.getEnum("isolation");
rbta.setIsolationLevel(isolation.value());
// 超时时间
rbta.setTimeout(attributes.getNumber("timeout").intValue());
// 是否只读
rbta.setReadOnly(attributes.getBoolean("readOnly"));
// 指定事务管理器
rbta.setQualifier(attributes.getString("value"));
List<RollbackRuleAttribute> rollbackRules = new ArrayList<>();
// 设置接收到哪些 Class 对象(异常)需要回滚
for (Class<?> rbRule : attributes.getClassArray("rollbackFor")) {
rollbackRules.add(new RollbackRuleAttribute(rbRule));
}
for (String rbRule : attributes.getStringArray("rollbackForClassName")) {
rollbackRules.add(new RollbackRuleAttribute(rbRule));
}
// 设置接收到哪些 Class 对象(异常)不需要回滚
for (Class<?> rbRule : attributes.getClassArray("noRollbackFor")) {
rollbackRules.add(new NoRollbackRuleAttribute(rbRule));
}
for (String rbRule : attributes.getStringArray("noRollbackForClassName")) {
rollbackRules.add(new NoRollbackRuleAttribute(rbRule));
}
rbta.setRollbackRules(rollbackRules);
return rbta;
}
}
解析过程比较简单,就是将 @Transactional
注解解析成 RuleBasedTransactionAttribute
对象(实现了 TransactionDefinition
接口),设置相关属性,不存在这个注解的话返回 null
小节
在这个 PointcutAdvisor 切面关联着一个 Pointcut 切点,为 TransactionAttributeSourcePointcut
对象,内部有一个 AnnotationTransactionAttributeSource
事务属性资源对象。在这个切点判断某个方法是否需要进行事务处理时,通过内部的 AnnotationTransactionAttributeSource
对象解析 @Transactional
注解(没有的话表示不匹配),解析过程需要借助于 SpringTransactionAnnotationParser
解析器解析 @Transactional
注解,将这个事务定义的一些属性封装成一个 RuleBasedTransactionAttribute
事务定义对象(实现了 TransactionDefinition
接口),并缓存
TransactionInterceptor 事务拦截处理
通过 Pointcut 事务切点筛选出来的 Bean,会创建一个代理对象,Bean 内部肯定定义了 @Transactional
注解,如果是类上定义的 @Transactional
注解,每个方法都需要进行事务处理。代理对象的事务拦截处理在 TransactionInterceptor 拦截器中,实现了 MethodInterceptor 方法拦截器,也就是实现了 Object invoke(MethodInvocation invocation)
这个方法,一起来看看 TransactionInterceptor 这个类
结构类图:
// TransactionInterceptor.java
@Override
@Nullable
public Object invoke(MethodInvocation invocation) throws Throwable {
// 目标类
Class<?> targetClass = (invocation.getThis() != null ? AopUtils.getTargetClass(invocation.getThis()) : null);
// 在事务中执行方法调用器
return invokeWithinTransaction(invocation.getMethod(), targetClass, invocation::proceed);
}
调用 invokeWithinTransaction(..)
方法,在事务中执行方法调用器,如下:
// TransactionAspectSupport.java
protected Object invokeWithinTransaction(Method method, @Nullable Class<?> targetClass,
final InvocationCallback invocation) throws Throwable {
// If the transaction attribute is null, the method is non-transactional.
TransactionAttributeSource tas = getTransactionAttributeSource();
// <1> 获取 `@Transactional` 注解对应的 TransactionAttribute 对象(如果在 AnnotationTransactionAttributeSource 解析过则取缓存)
final TransactionAttribute txAttr = (tas != null ? tas.getTransactionAttribute(method, targetClass) : null);
// <2> 获取 PlatformTransactionManager 事务管理器(可以指定,没有指定则获取默认的)
final PlatformTransactionManager tm = determineTransactionManager(txAttr);
// <3> 获取方法的唯一标识,默认都是 `类名.方法名`
final String joinpointIdentification = methodIdentification(method, targetClass, txAttr);
// <4> 如果已有 `@Transactional` 注解对应的 TransactionAttribute 对象,或者是一个非回调偏向的事务管理器(默认不是)
if (txAttr == null || !(tm instanceof CallbackPreferringPlatformTransactionManager)) {
// Standard transaction demarcation with getTransaction and commit/rollback calls.
// <4.1> 创建 TransactionInfo 事务信息对象,绑定在 ThreadLocal 中
// 包含一个 DefaultTransactionStatus 事务状态对象
TransactionInfo txInfo = createTransactionIfNecessary(tm, txAttr, joinpointIdentification);
Object retVal;
try {
// <4.2> 继续执行方法调用器
retVal = invocation.proceedWithInvocation();
}
catch (Throwable ex) {
// <4.3> 如果捕获到异常,则在这里完成事务,进行回滚或者提交
completeTransactionAfterThrowing(txInfo, ex);
throw ex;
}
finally {
// <4.4> `finally` 语句块,释放 ThreadLocal 中的 TransactionInfo 对象,设置为上一个事务信息对象(没有的话为空)
cleanupTransactionInfo(txInfo);
}
// <4.5> 正常情况,到这里完成事务
commitTransactionAfterReturning(txInfo);
// <4.6> 返回执行结果
return retVal;
}
// <5> 否则,就是支持回调的事务管理器,编程式事务(回调偏向),暂时忽略
else {
// .....
}
}
整个过程有点复杂,我们一步一步来看
- 获取
@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 是如何创建一个事务的
1. 创建事务
createTransactionIfNecessary(..)
方法,创建一个事务(如果有必要的话),如下:
// TransactionAspectSupport.java
protected TransactionInfo createTransactionIfNecessary(@Nullable PlatformTransactionManager tm,
@Nullable TransactionAttribute txAttr, final String joinpointIdentification) {
// <1> 没有设置事务名称,则封装成一个 DelegatingTransactionAttribute 委托对象,支持返回一个事务名称(类名.方法名)
if (txAttr != null && txAttr.getName() == null) {
txAttr = new DelegatingTransactionAttribute(txAttr) {
@Override
public String getName() {
return joinpointIdentification;
}
};
}
// <2> 获取一个 TransactionStatus 对象
TransactionStatus status = null;
if (txAttr != null) {
// <2.1> 如果存在事务管理器
if (tm != null) {
// 从事务管理器中获取一个 TransactionStatus 事务状态对象(对事务的封装),该对象包含以下信息:
// TransactionDefinition 事务定义、DataSourceTransactionObject 数据源事务对象(包括 DataSource 和 Connection)、
// 是否是一个新的事务、是否是一个新的事务同步器、被挂起的事务资源对象
status = tm.getTransaction(txAttr);
}
// <2.2> 否则,跳过
else { }
}
// <3> 创建一个 TransactionInfo 事务信息对象,并绑定到 ThreadLocal 中
return prepareTransactionInfo(tm, txAttr, joinpointIdentification, status);
}
过程如下:
如果没有设置事务名称,则封装成一个 DelegatingTransactionAttribute 委托对象,支持返回一个事务名称(
类名.方法名
)获取一个
TransactionStatus
对象(对事务的封装)- 如果存在事务管理器,Spring Boot 中默认为
DataSourceTransactionManager
,则通过事务管理器根据@Transactional
注解获取一个TransactionStatus
事务状态对象,该对象是对事务的封装,包含了以下信息:TransactionDefinition
事务定义DataSourceTransactionObject
数据源事务对象(包括 DataSource 和 Connection)- 是否是一个新的事务
- 是否是一个新的事务同步器
- 被挂起的事务资源对象(如果有)
- 否则,跳过
- 如果存在事务管理器,Spring Boot 中默认为
创建一个
TransactionInfo
事务信息对象,并绑定到 ThreadLocal 中,如下:// TransactionAspectSupport.java
protected TransactionInfo prepareTransactionInfo(@Nullable PlatformTransactionManager tm,
@Nullable TransactionAttribute txAttr, String joinpointIdentification,
@Nullable 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 中
我们继续看到在上面第 2
步 PlatformTransactionManager
事务管理器是如何创建一个 Spring 事务的
1.1 getTransaction 方法
PlatformTransactionManager 事务管理器接口的类图:
该接口就定义了三个方法,如下:
public interface PlatformTransactionManager {
TransactionStatus getTransaction(@Nullable TransactionDefinition definition)
throws TransactionException;
void commit(TransactionStatus status) throws TransactionException;
void rollback(TransactionStatus status) throws TransactionException;
}
三个方法分别对应创建事务,提交,回滚三个操作,关于 Spring 事务也就这三个核心步骤了,我们先来看看 getTransaction(..)
这个方法的实现,如下:
// AbstractPlatformTransactionManager.java
@Override
public final TransactionStatus getTransaction(@Nullable TransactionDefinition definition) throws TransactionException {
// <1> 先从当前事务管理器中获取 DataSource 对象,然后尝试以它作为一个 Key 从一个 ThreadLocal 的 Map 中获取对应的 ConnectionHolder 连接对象
// 会包装成一个 DataSourceTransactionObject 对象返回
Object transaction = doGetTransaction();
// Cache debug flag to avoid repeated checks.
boolean debugEnabled = logger.isDebugEnabled();
// <2> 如果没有 @Transactional 注解对应的元信息,则创建一个默认的 TransactionDefinition 对象
if (definition == null) {
// Use defaults if no transaction definition given.
definition = new DefaultTransactionDefinition();
}
// <3> 如果上面 `transaction` 数据源事务对象已有 Connection 连接,且正处于一个事务中,表示当前线程已经在一个事务中了
if (isExistingTransaction(transaction)) {
// Existing transaction found -> check propagation behavior to find out how to behave.
// <3.1> 根据 Spring 事务传播级别进行不同的处理,同时创建一个 DefaultTransactionStatus 事务状态对象,包含以下信息:
// TransactionDefinition 事务定义、DataSourceTransactionObject 数据源事务对象、
// 是否需要新创建一个事务、是否需要一个新的事务同步器、被挂起的事务资源对象
return handleExistingTransaction(definition, transaction, debugEnabled);
}
// <4> 否则,当前线程没有事务
// 超时不能小于默认值
if (definition.getTimeout() < TransactionDefinition.TIMEOUT_DEFAULT) {
throw new InvalidTimeoutException("Invalid transaction timeout", definition.getTimeout());
}
// <4.1> 如果是 **MANDATORY** 事务传播级别(当前线程已经在一个事务中,则加入该事务,否则抛出异常),因为当前线程没有事务,此时抛出异常
if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_MANDATORY) {
throw new IllegalTransactionStateException(
"No existing transaction found for transaction marked with propagation 'mandatory'");
}
// <4.2> 否则,如果事务传播级别为 **REQUIRED|REQUIRES_NEW|NESTED**
else if (
// 如果当前线程已经在一个事务中,则加入该事务,否则新建一个事务(默认)
definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRED ||
// 无论如何都会创建一个新的事务,如果当前线程已经在一个事务中,则挂起当前事务,创建一个新的事务
definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRES_NEW ||
// 执行一个嵌套事务
definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NESTED)
{
// <4.2.1> 创建一个“空”的挂起资源对象
SuspendedResourcesHolder suspendedResources = suspend(null);
try {
// 是否需要新的事务同步器,默认为 true
boolean newSynchronization = (getTransactionSynchronization() != SYNCHRONIZATION_NEVER);
// <4.2.2> 创建一个 DefaultTransactionStatus 事务状态对象,设置相关属性
// 这里 `newTransaction` 参数为 `true`,表示是一个新的事务
DefaultTransactionStatus status = newTransactionStatus(
definition, transaction, true, newSynchronization, debugEnabled, suspendedResources);
// <4.2.3> 【关键】执行 begin 操作,如果没有 Connection 数据库连接,则通过 DataSource 创建一个新的连接
// 设置 Connection 的隔离级别、是否只读,并执行 Connection#setAutoCommit(false) 方法,不自动提交
// 同时将 DataSource(数据源对象)和 ConnectionHolder(数据库连接持有者)保存至 ThreadLocal 中
doBegin(transaction, definition);
// <4.2.4> 借助 TransactionSynchronizationManager 事务同步管理器设置相关 ThreadLocal 变量
prepareSynchronization(status, definition);
// <4.2.5> 返回上面创建的 DefaultTransactionStatus 事务状态对象
return status;
} catch (RuntimeException | Error ex) {
resume(null, suspendedResources);
throw ex;
}
}
// <4.3> 否则,创建一个“空”的事务状态对象
else {
// Create "empty" transaction: no actual transaction, but potentially synchronization.
if (definition.getIsolationLevel() != TransactionDefinition.ISOLATION_DEFAULT && logger.isWarnEnabled()) {
logger.warn("Custom isolation level specified but no actual transaction initiated; " +
"isolation level will effectively be ignored: " + definition);
}
// 是否需要新的事务同步器,默认为 true
boolean newSynchronization = (getTransactionSynchronization() == SYNCHRONIZATION_ALWAYS);
// 创建一个 DefaultTransactionStatus 事务状态对象,设置相关属性,这里也是一个新的事务
return prepareTransactionStatus(definition, null, true, newSynchronization, debugEnabled, null);
}
}
整个过程如下:
先从当前事务管理器中获取 DataSource 对象,然后尝试以它作为一个 Key 从一个 ThreadLocal 的 Map 中获取对应的
ConnectionHolder
连接对象(没有就是null
),最终包装成一个DataSourceTransactionObject
对象返回// DataSourceTransactionManager.java
@Override
protected Object doGetTransaction() {
DataSourceTransactionObject txObject = new DataSourceTransactionObject();
txObject.setSavepointAllowed(isNestedTransactionAllowed());
// 从事务同步管理器根据 DataSource 获取 Connection 连接持有者
// 就是从它的 ThreadLocal 中获取
ConnectionHolder conHolder =
(ConnectionHolder) TransactionSynchronizationManager.getResource(obtainDataSource());
txObject.setConnectionHolder(conHolder, false);
return txObject;
}
// TransactionSynchronizationManager.java
/**
* 事务资源,两种数据键值对
* 1. 会话工厂和会话,SqlSessionFactory -> SqlSessionHolder
* 2. 数据源和连接,DataSource -> ConnectionHolder
*/
private static final ThreadLocal<Map<Object, Object>> resources =
new NamedThreadLocal<>("Transactional resources");
如果没有
@Transactional
注解对应的元信息,则创建一个默认的TransactionDefinition
对象如果上面
transaction
数据源事务对象已有 Connection 连接,且正处于一个事务中,表示当前线程已经在一个事务中了// DataSourceTransactionManager.java
@Override
protected boolean isExistingTransaction(Object transaction) {
// 获取这个数据源事务对象
DataSourceTransactionObject txObject = (DataSourceTransactionObject) transaction;
// 是否已有 Connection 连接,且正处于一个事务中
return (txObject.hasConnectionHolder() && txObject.getConnectionHolder().isTransactionActive());
}
- 调用
handleExistingTransaction(..)
方法,根据 Spring 事务传播级别进行不同的处理,同时创建一个DefaultTransactionStatus
事务状态对象并返回
- 调用
否则,当前线程没有事务
- 如果是 MANDATORY 事务传播级别(当前线程已经在一个事务中,则加入该事务,否则抛出异常),因为当前线程没有事务,此时抛出异常
- 否则,如果事务传播级别为 REQUIRED | REQUIRES_NEW | NESTED
- 创建一个“空”的被挂起的资源对象
- 创建一个
DefaultTransactionStatus
事务状态对象,设置相关属性,这里newTransaction
参数为true
(记住),表示是一个新的事务 - 【关键】调用
doBegin(..)
方法,执行 begin 操作,如果没有 Connection 数据库连接,则通过 DataSource 创建一个新的连接;会设置 Connection 的隔离级别、是否只读,并执行Connection#setAutoCommit(false)
方法,不自动提交;同时将 DataSource(数据源对象)和ConnectionHolder
(数据库连接持有者)保存至 ThreadLocal 中 - 借助
TransactionSynchronizationManager
事务同步管理器设置相关 ThreadLocal 变量,例如当前事务的隔离级别、是否只读、是否处于事务中等 - 返回上面创建的
DefaultTransactionStatus
事务状态对象
- 否则,创建一个“空”的事务状态对象
- 创建一个
DefaultTransactionStatus
事务状态对象,不使用事务
- 创建一个
整个处理过程稍微有点复杂,不过流程非常清晰,当没有事务时,根据事务的传播级别决定是否需要创建一个事务,创建过程主要在上面的第 4.2.3
步;当正处于一个事务中时,在 3.1
步,根据事务的传播级别判断是否需要创建一个新的事务,或者加入该事务等操作;接下来我们来看看这两种情况
1.2 doBegin 方法
doBegin(..)
方法,创建一个新的事务,如下:
// AbstractPlatformTransactionManager.java
@Override
protected void doBegin(Object transaction, TransactionDefinition definition) {
DataSourceTransactionObject txObject = (DataSourceTransactionObject) transaction;
Connection con = null;
try {
// <1> 如果没有 Connection 数据库连接,或者连接处于事务同步状态
if (!txObject.hasConnectionHolder() ||
txObject.getConnectionHolder().isSynchronizedWithTransaction()) {
// <1.1> 通过 DataSource 创建一个 Connection 数据库连接
Connection newCon = obtainDataSource().getConnection();
if (logger.isDebugEnabled()) {
logger.debug("Acquired Connection [" + newCon + "] for JDBC transaction");
}
// <1.2> 重置 ConnectionHolder 连接持有者,封装刚创建的数据库连接
txObject.setConnectionHolder(new ConnectionHolder(newCon), true);
}
// <2> 设置ConnectionHolder 连接持有者处于事务同步中
txObject.getConnectionHolder().setSynchronizedWithTransaction(true);
// <3> 获取 Connection 数据库连接
con = txObject.getConnectionHolder().getConnection();
// <4> 设置 Connection 是否只读和事务隔离性
Integer previousIsolationLevel = DataSourceUtils.prepareConnectionForTransaction(con, definition);
// <5> 保存之前的事务隔离级别(被挂起的事务)
txObject.setPreviousIsolationLevel(previousIsolationLevel);
// Switch to manual commit if necessary. This is very expensive in some JDBC drivers,
// so we don't want to do it unnecessarily (for example if we've explicitly
// configured the connection pool to set it already).
// <6> 将自动提交关闭
if (con.getAutoCommit()) {
txObject.setMustRestoreAutoCommit(true);
if (logger.isDebugEnabled()) {
logger.debug("Switching JDBC Connection [" + con + "] to manual commit");
}
con.setAutoCommit(false);
}
// <7> 如果需要强制设置只读(默认不需要),且连接本身是只读的,则这里提前设置事务的只读性
prepareTransactionalConnection(con, definition);
// <8> 当前 Connection 数据库连接正处于一个事务中
txObject.getConnectionHolder().setTransactionActive(true);
// <9> 设置超时时间
int timeout = determineTimeout(definition);
if (timeout != TransactionDefinition.TIMEOUT_DEFAULT) {
txObject.getConnectionHolder().setTimeoutInSeconds(timeout);
}
// Bind the connection holder to the thread.
// <10> 如果是新的事务,则将 DataSource(数据源对象)和 ConnectionHolder(数据库连接持有者)保存至 ThreadLocal 中
if (txObject.isNewConnectionHolder()) {
TransactionSynchronizationManager.bindResource(obtainDataSource(), txObject.getConnectionHolder());
}
}
catch (Throwable ex) {
if (txObject.isNewConnectionHolder()) {
DataSourceUtils.releaseConnection(con, obtainDataSource());
txObject.setConnectionHolder(null, false);
}
throw new CannotCreateTransactionException("Could not open JDBC Connection for transaction", ex);
}
}
过程如下:
- 如果没有 Connection 数据库连接,或者连接处于事务同步状态
- 通过 DataSource 创建一个 Connection 数据库连接
- 重置
ConnectionHolder
连接持有者,封装刚创建的数据库连接
- 设置
ConnectionHolder
连接持有者处于事务同步中 - 获取 Connection 数据库连接,设置是否只读、事务隔离性、超时时间,并将
autocommit
设置为fasle
,不自动提交 - 保存之前的事务隔离级别(被挂起的事务)
- 如果需要强制设置只读(默认不需要),且连接本身是只读的,则这里提前设置事务的只读性
- 设置当前 ConnectionHolder 数据库连接正处于一个事务中
- 如果是新的事务,则将 DataSource(数据源对象)和 ConnectionHolder(数据库连接持有者)保存至 ThreadLocal 中
整个过程关键在于第 3
步将 autocommit
设置为 false
,不会自动提交,这样一来,可以在一个事务中根据行为作出相应的操作,例如出现异常进行回滚,没有问题则进行提交
接下来我们来看看 Spring 对于当前线程正处于一个事务中时,如何进行处理的
1.3 handleExistingTransaction 方法
handleExistingTransaction(..)
方法,处理已存在事务的情况,如下:
// AbstractPlatformTransactionManager.java
private TransactionStatus handleExistingTransaction(
TransactionDefinition definition, Object transaction, boolean debugEnabled)
throws TransactionException {
// <1> 如果是 **NEVER** 事务传播级别,因为当前线程正处于一个事务中,此时抛出异常
if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NEVER) {
throw new IllegalTransactionStateException(
"Existing transaction found for transaction marked with propagation 'never'");
}
// <2> 否则,如果是 **NOT_SUPPORTED** 事务传播级别,因为当前线程正处于一个事务中,此时挂起事务
if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NOT_SUPPORTED) {
// <2.1> 事务挂起,也就是从 ThreadLocal 中移除各种对象,并返回一个挂起的资源对象(包含所有被移除的对象)
Object suspendedResources = suspend(transaction);
// 是否需要一个创建一个事务同步器(默认为 true)
boolean newSynchronization = (getTransactionSynchronization() == SYNCHRONIZATION_ALWAYS);
// <2.2> 创建一个 DefaultTransactionStatus 事务状态对象,设置相关属性,包括被挂起的资源
// 设置 `transaction` 为 null(当前没有事务),`newTransaction` 为 `false`,表示不是一个新的事务
// 同时借助 TransactionSynchronizationManager 事务同步管理器设置相关 ThreadLocal 变量
return prepareTransactionStatus(
definition, null, false, newSynchronization, debugEnabled, suspendedResources);
}
// <3> 否则,如果是 **REQUIRES_NEW** 事务传播级别(无论如何都会创建一个新的事务),因为当前线程正处于一个事务中,此时挂起当前事务,创建一个新的事务
if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRES_NEW) {
// <3.1> 事务挂起,也就是从 ThreadLocal 中移除各种对象,并返回一个挂起的资源对象(包含所有被移除的对象)
SuspendedResourcesHolder suspendedResources = suspend(transaction);
try {
// 是否需要一个创建一个事务同步器(默认为 true)
boolean newSynchronization = (getTransactionSynchronization() != SYNCHRONIZATION_NEVER);
// <3.2> 创建一个事务状态对象,设置相关属性,包括被挂起的资源
// 设置 `newTransaction` 为 `true`,表示是一个新的事务
DefaultTransactionStatus status = newTransactionStatus(
definition, transaction, true, newSynchronization, debugEnabled, suspendedResources);
// <3.3> 【关键】执行 begin 操作,如果没有 Connection 数据库连接,则通过 DataSource 创建一个新的连接
// 设置 Connection 的隔离级别、是否只读,并执行 Connection#setAutoCommit(false) 方法,不自动提交
// 同时将 DataSource(数据源对象)和 ConnectionHolder(数据库连接持有者)保存至 ThreadLocal 中
doBegin(transaction, definition);
// <3.4> 借助 TransactionSynchronizationManager 事务同步管理器设置相关 ThreadLocal 变量
prepareSynchronization(status, definition);
// <3.5> 返回上面创建的 DefaultTransactionStatus 事务状态对象
return status;
}
catch (RuntimeException | Error beginEx) {
// 在抛出异常前唤醒刚才被挂起的资源
resumeAfterBeginException(transaction, suspendedResources, beginEx);
throw beginEx;
}
}
// <4> 否则,如果是 **NESTED** 事务传播级别(执行一个嵌套事务),还是使用当前线程的事务,不过设置了保存点,相当于一个嵌套事务,在 Mysql 中是采用 SAVEPOINT 来实现的
if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NESTED) {
if (!isNestedTransactionAllowed()) {
throw new NestedTransactionNotSupportedException(
"Transaction manager does not allow nested transactions by default - " +
"specify 'nestedTransactionAllowed' property with value 'true'");
}
// <4.1> 如果支持使用保存点(默认为 true)
if (useSavepointForNestedTransaction()) {
// Create savepoint within existing Spring-managed transaction,
// through the SavepointManager API implemented by TransactionStatus.
// Usually uses JDBC 3.0 savepoints. Never activates Spring synchronization.
// 创建一个事务状态对象,设置相关属性,这里设置了不是一个新的事务,也不是一个新的事务同步器
DefaultTransactionStatus status =
prepareTransactionStatus(definition, transaction, false, false, debugEnabled, null);
// 创建一个保存点,调用 Connection#setSavepoint(String) 方法
status.createAndHoldSavepoint();
return status;
}
// <4.2> 否则,例如 JtaTransactionManager(JTA 事务),暂时忽略
else {
// Nested transaction through nested begin and commit/rollback calls.
// Usually only for JTA: Spring synchronization might get activated here
// in case of a pre-existing JTA transaction.
boolean newSynchronization = (getTransactionSynchronization() != SYNCHRONIZATION_NEVER);
// 创建一个事务状态对象,设置相关属性,这里设置了是一个新的事务
DefaultTransactionStatus status = newTransactionStatus(
definition, transaction, true, newSynchronization, debugEnabled, null);
// 执行 begin 操作,暂时忽略
doBegin(transaction, definition);
prepareSynchronization(status, definition);
return status;
}
}
// Assumably PROPAGATION_SUPPORTS or PROPAGATION_REQUIRED.
// <5> 走到这里了,表示就使用当前已存在的事务,例如 **REQUIRED** 传播级别
// <6> 判断定义的 IsolationLevel 和已存在的事务的 IsolationLevel 进行校验(默认为 false)
if (isValidateExistingTransaction()) {
if (definition.getIsolationLevel() != TransactionDefinition.ISOLATION_DEFAULT) {
Integer currentIsolationLevel = TransactionSynchronizationManager.getCurrentTransactionIsolationLevel();
if (currentIsolationLevel == null || currentIsolationLevel != definition.getIsolationLevel()) {
Constants isoConstants = DefaultTransactionDefinition.constants;
throw new IllegalTransactionStateException("Participating transaction with definition [" +
definition + "] specifies isolation level which is incompatible with existing transaction: " +
(currentIsolationLevel != null ?
isoConstants.toCode(currentIsolationLevel, DefaultTransactionDefinition.PREFIX_ISOLATION) :
"(unknown)"));
}
}
if (!definition.isReadOnly()) {
if (TransactionSynchronizationManager.isCurrentTransactionReadOnly()) {
throw new IllegalTransactionStateException("Participating transaction with definition [" +
definition + "] is not marked as read-only but existing transaction is");
}
}
}
// 是否是一个新的事务同步器(默认为 true)
boolean newSynchronization = (getTransactionSynchronization() != SYNCHRONIZATION_NEVER);
// <7> 创建一个 DefaultTransactionStatus 事务状态对象
// 设置 `newTransaction` 为 `false`,表示不是一个新的事务
// 同时借助 TransactionSynchronizationManager 事务同步管理器设置相关 ThreadLocal 变量
// 注意这里用的 `definition` 是当前 `@Transactional` 注解的相关属性,所以隔离级别等属性是当前定义的,而不是当前已存在的事务的隔离级别
return prepareTransactionStatus(definition, transaction, false, newSynchronization, debugEnabled, null);
}
整个过程如下:
- 如果是 NEVER 事务传播级别,因为当前线程正处于一个事务中,此时抛出异常
- 否则,如果是 NOT_SUPPORTED 事务传播级别,因为当前线程正处于一个事务中,此时挂起事务,不使用事务
- 调用
suspend(..)
方法,将当前事务挂起,也就是从 ThreadLocal 中移除各种对象,并返回一个挂起的资源对象(包含所有被移除的对象) - 创建一个
DefaultTransactionStatus
事务状态对象,设置相关属性,包括被挂起的资源,会设置transaction
为null
(不使用事务),同时借助TransactionSynchronizationManager
事务同步管理器设置相关 ThreadLocal 变量
- 调用
- 否则,如果是 REQUIRES_NEW 事务传播级别(无论如何都会创建一个新的事务),因为当前线程正处于一个事务中,此时挂起当前事务,创建一个新的事务
- 调用
suspend(..)
方法,将当前事务挂起,也就是从 ThreadLocal 中移除各种对象,并返回一个挂起的资源对象(包含所有被移除的对象) - 创建一个事务状态对象,设置相关属性,包括被挂起的资源,会设置
newTransaction
为true
,表示是一个新的事务 - 【关键】调用
doBegin(..)
方法,执行 begin 操作,前面的1.2
小节已经分析过,不再赘述 - 借助
TransactionSynchronizationManager
事务同步管理器设置相关 ThreadLocal 变量 - 返回上面创建的
DefaultTransactionStatus
事务状态对象
- 调用
- 否则,如果是 NESTED 事务传播级别(执行一个嵌套事务),还是使用当前线程的事务,不过设置了保存点,相当于一个嵌套事务,在 Mysql 中是采用 SAVEPOINT 来实现的
- 如果支持使用保存点(默认为 true)
- 创建一个事务状态对象,设置相关属性,这里设置了
newTransaction
为false
,表示不是一个新的事务 - 创建一个保存点,调用
Connection#setSavepoint(String)
方法 - 返回上面创建的
DefaultTransactionStatus
事务状态对象
- 创建一个事务状态对象,设置相关属性,这里设置了
- 否则,例如 JtaTransactionManager(JTA 事务),暂时忽略
- 如果支持使用保存点(默认为 true)
- 走到这里了,表示就使用当前已存在的事务,也就是SUPPORTS和REQUIRED 两种传播级别
- 判断是否需要对定义的隔离级别和已存在的事务的隔离级别进行校验(默认为
false
) - 创建一个
DefaultTransactionStatus
事务状态对象,设置newTransaction
为false
,表示不是一个新的事务,还是使用当前事务;同时借助TransactionSynchronizationManager
事务同步管理器设置相关 ThreadLocal 变量;注意这里用的definition
是当前@Transactional
注解的相关属性,所以隔离级别等属性是当前定义的,而不是当前已存在的事务的隔离级别
整个处理过程并不复杂,其中挂起当前事务会调用 suspend(..)
方法,我们一起来看看
1.4 suspend 方法
suspend(..)
方法,如果当前正处于一个事务中,传播级别为 NOT_SUPPORTED 或者 REQUIRES_NEW,则需要挂起当前事务,然后不使用事务或者创建一个新的事务,如下:
// AbstractPlatformTransactionManager.java
protected final SuspendedResourcesHolder suspend(@Nullable Object transaction) throws TransactionException {
// <1> 当前线程已有 TransactionSynchronization 事务同步器
if (TransactionSynchronizationManager.isSynchronizationActive()) {
// <1.1> 将当前线程的 TransactionSynchronization 全部挂起,也就是从 ThreadLocal 中移除,并返回挂起的对象
List<TransactionSynchronization> suspendedSynchronizations = doSuspendSynchronization();
try {
Object suspendedResources = null;
if (transaction != null) {
// <1.2> 挂起事务,也就是从 ThreadLocal 中移除,并返回挂起的 ConnectionHolder 对象
suspendedResources = doSuspend(transaction);
}
// <1.3> 解除绑定当前事务各种属性,名称、只读、隔离级别、是否是真实的事务
String name = TransactionSynchronizationManager.getCurrentTransactionName();
TransactionSynchronizationManager.setCurrentTransactionName(null);
boolean readOnly = TransactionSynchronizationManager.isCurrentTransactionReadOnly();
TransactionSynchronizationManager.setCurrentTransactionReadOnly(false);
Integer isolationLevel = TransactionSynchronizationManager.getCurrentTransactionIsolationLevel();
TransactionSynchronizationManager.setCurrentTransactionIsolationLevel(null);
boolean wasActive = TransactionSynchronizationManager.isActualTransactionActive();
TransactionSynchronizationManager.setActualTransactionActive(false);
// <1.4> 返回被挂起的资源对象(对上面被挂起的对象进行封装)
return new SuspendedResourcesHolder(
suspendedResources, suspendedSynchronizations, name, readOnly, isolationLevel, wasActive);
}
catch (RuntimeException | Error ex) {
// doSuspend failed - original transaction is still active...
// 在抛出异常前唤醒刚才被挂起的资源
doResumeSynchronization(suspendedSynchronizations);
throw ex;
}
}
// <2> 否则,如果当前数据源事务对象不为空,则挂起
else if (transaction != null) {
// Transaction active but no synchronization active.
// <2.1> 挂起事务,也就是从 ThreadLocal 中移除,并返回挂起的 ConnectionHolder 对象
Object suspendedResources = doSuspend(transaction);
// <2.2> 返回被挂起的资源对象(对上面被挂起的对象进行封装)
return new SuspendedResourcesHolder(suspendedResources);
}
// <3> 否则,什么都不用做,返回一个空对象
else {
// Neither transaction nor synchronization active.
return null;
}
}
挂起当前事务的过程如下:
如果当前线程已有
TransactionSynchronization
事务同步器将当前线程的
TransactionSynchronization
全部挂起,也就是从 ThreadLocal 中移除,并返回挂起的对象挂起事务,也就是从 ThreadLocal 中移除,并返回挂起的
ConnectionHolder
对象// DataSourceTransactionManager.java
@Override
protected Object doSuspend(Object transaction) {
DataSourceTransactionObject txObject = (DataSourceTransactionObject) transaction;
// 将 Connection 连接持有者置空
txObject.setConnectionHolder(null);
// 将 ThreadLocal 中的 DataSource 移除
return TransactionSynchronizationManager.unbindResource(obtainDataSource());
}
解除绑定当前事务各种属性,名称、只读、隔离级别、是否是真实的事务
返回被挂起的资源对象(对上面被挂起的对象进行封装)
否则,如果当前数据源事务对象不为空,则挂起
- 挂起事务,和
1.2
步相同 - 返回被挂起的资源对象(对上面被挂起的对象进行封装)
- 挂起事务,和
否则,什么都不用做,返回一个空对象
可以看到,挂起当前事务就是从 ThreadLocal 中移除相关资源,会将这些资源封装成一个对象返回,因为后续需要唤醒这些被挂起的资源(重新设置到 ThreadLocal 中)
小节
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 创建事务的内容差不多就结束了,接下来我们来看看 Spring 是如何提交一个事务的
2. 提交事务
在 TransactionInterceptor
事务拦截处理过程中,如果方法的执行过程没有抛出异常,那么此时我们是不是需要调用 Connection#commit()
方法,提交本次事务,我们一起来看看 Spring 的处理过程
// TransactionAspectSupport.java
protected void commitTransactionAfterReturning(@Nullable TransactionInfo txInfo) {
if (txInfo != null && txInfo.getTransactionStatus() != null) {
// 通过 DataSourceTransactionManager 提交当前事务
txInfo.getTransactionManager().commit(txInfo.getTransactionStatus());
}
}
可以看到是通过 DataSourceTransactionManager 提交当前事务
2.1 commit 方法
// AbstractPlatformTransactionManager.java
@Override
public final void commit(TransactionStatus status) throws TransactionException {
// <1> 如果事务已完成,此时又提交,则抛出异常
if (status.isCompleted()) {
throw new IllegalTransactionStateException(
"Transaction is already completed - do not call commit or rollback more than once per transaction");
}
DefaultTransactionStatus defStatus = (DefaultTransactionStatus) status;
// <2> 事务明确标记为回滚
if (defStatus.isLocalRollbackOnly()) {
// 进行回滚过程(预料之中)
processRollback(defStatus, false);
return;
}
// <3> 判断全局回滚时是否需要提交(默认不需要),且当前事务为全局回滚
// 例如 **REQUIRED** 传播级别,当已有一个事务时则加入其中,此时如果抛出异常,则会设置为全局回滚,那么当事务进行提交时,对于整个事务都需要回滚
if (!shouldCommitOnGlobalRollbackOnly() && defStatus.isGlobalRollbackOnly()) {
// 进行回滚过程(预料之外)
processRollback(defStatus, true);
return;
}
// <4> 执行提交事务
processCommit(defStatus);
}
完成事务的过程如下:
如果事务已完成,此时又提交,则抛出异常
事务明确标记为回滚(暂时忽略),则调用
processRollback(..)
方法进行“回滚”判断全局回滚时是否需要提交(默认不需要),且当前事务为全局回滚,则调用
processRollback(..)
方法进行“回滚”例如REQUIRED传播级别,当已有一个事务时则加入其中,此时如果抛出异常,则会设置为全局回滚,那么当事务进行提交时,对于整个事务都需要回滚
调用
processCommit(..)
方法,执行提交事务
可以看到并不一定会“提交”,当标记需要全局回滚的状态时会进行“回滚”,这一小节我们重点关注第 4
步
2.2 processCommit 方法
processCommit(..)
方法,执行事务的提交过程,如下:
// AbstractPlatformTransactionManager.java
private void processCommit(DefaultTransactionStatus status) throws TransactionException {
try {
boolean beforeCompletionInvoked = false;
try {
boolean unexpectedRollback = false;
// <1> 进行三个前置操作
prepareForCommit(status); // <1.1> 在 Spring 中为空方法
// <1.2> 调用 TransactionSynchronization#beforeCommit 方法
// 例如在 Mybatis-Spring 中的 SqlSessionSynchronization 会调用其 SqlSession#commit() 方法,提交批量操作,刷新缓存
triggerBeforeCommit(status);
// <1.3> 调用 TransactionSynchronization#beforeCompletion 方法
// 由 Spring 事务托管,不是真的关闭连接,从 ThreadLocal 中删除 DataSource 和 ConnectionHolder 的映射关系
// 例如在 Mybatis-Spring 中的 SqlSessionSynchronization 中,会从 ThreadLocal 中删除 SqlSessionFactory 和 SqlSessionHolder 的映射关系,
// 且调用其 SqlSession#close() 方法
triggerBeforeCompletion(status);
// <2> 标记三个前置操作已完成
beforeCompletionInvoked = true;
// <3> 有保存点,即嵌套事务
if (status.hasSavepoint()) {
unexpectedRollback = status.isGlobalRollbackOnly();
// <3.1> 释放保存点,等外层的事务进行提交
status.releaseHeldSavepoint();
}
// <4> 否则,如果是一个新的事务
else if (status.isNewTransaction()) {
unexpectedRollback = status.isGlobalRollbackOnly();
// <4.1> 提交事务,执行 Connection#commit() 方法
doCommit(status);
}
// <5> 否则,在事务被标记为全局回滚的情况下是否提前失败(默认为 false)
else if (isFailEarlyOnGlobalRollbackOnly()) {
unexpectedRollback = status.isGlobalRollbackOnly();
}
// Throw UnexpectedRollbackException if we have a global rollback-only
// marker but still didn't get a corresponding exception from commit.
// 如果全局标记为仅回滚,但是提交时没有得到异常,则这里抛出异常
// 目的是需要回滚
if (unexpectedRollback) {
throw new UnexpectedRollbackException(
"Transaction silently rolled back because it has been marked as rollback-only");
}
}
catch (UnexpectedRollbackException ex) {
// can only be caused by doCommit
// 触发完成后事务同步,状态为回滚
triggerAfterCompletion(status, TransactionSynchronization.STATUS_ROLLED_BACK);
throw ex;
}
// 事务异常
catch (TransactionException ex) {
// can only be caused by doCommit
// 提交失败回滚
if (isRollbackOnCommitFailure()) {
doRollbackOnCommitException(status, ex);
}
// 触发完成后回调,事务同步状态为未知
else {
triggerAfterCompletion(status, TransactionSynchronization.STATUS_UNKNOWN);
}
throw ex;
}
// 运行时异常或者其它异常
catch (RuntimeException | Error ex) {
// 如果上面三个前置步骤未完成,调用最后一个前置步骤,即调用 TransactionSynchronization#beforeCompletion 方法
if (!beforeCompletionInvoked) {
triggerBeforeCompletion(status);
}
// 提交异常回滚
doRollbackOnCommitException(status, ex);
throw ex;
}
// Trigger afterCommit callbacks, with an exception thrown there
// propagated to callers but the transaction still considered as committed.
try {
// <6> 触发提交后的回调,调用 TransactionSynchronization#afterCommit 方法
// JMS 会有相关操作,暂时忽略
triggerAfterCommit(status);
}
finally {
// <7> 触发完成后的回调,事务同步状态为已提交,调用 TransactionSynchronization#afterCompletion 方法
// 例如在 Mybatis-Spring 中的 SqlSessionSynchronization 中,会从 ThreadLocal 中删除 SqlSessionFactory 和 SqlSessionHolder 的映射关系,
// 且调用其 SqlSession#close() 方法,解决可能出现的跨线程的情况
triggerAfterCompletion(status, TransactionSynchronization.STATUS_COMMITTED);
}
}
finally {
// <8> 在完成后清理,清理相关资源,“释放”连接,唤醒被挂起的资源
cleanupAfterCompletion(status);
}
}
提交过程如下:
进行三个前置操作
- 准备工作,在 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()
方法// DataSourceTransactionManager.java
@Override
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);
}
}
否则,在事务被标记为全局回滚的情况下是否提前失败(默认为
false
)触发提交后的回调,调用
TransactionSynchronization#afterCommit
方法,JMS 会有相关操作,暂时忽略触发完成后的回调,事务同步状态为已提交,调用
TransactionSynchronization#afterCompletion
方法,例如在 Mybatis-Spring 中的 SqlSessionSynchronization 中,会从 ThreadLocal 中删除 SqlSessionFactory 和 SqlSessionHolder 的映射关系,且调用其SqlSession#close()
方法,解决可能出现的跨线程的情况在完成后清理,清理相关资源,“释放”连接,唤醒被挂起的资源,如下:
// 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()
方法提交事务;其他情况不会真的提交事务,在这里仅清理相关资源,唤醒被挂起的资源
3. 回滚事务
在 TransactionInterceptor
事务拦截处理过程中,如果方法的执行过程抛出异常,那么此时我们是不是需要调用 Connection#roback()
方法,对本次事务进行回滚,我们一起来看看 Spring 的处理过程
// TransactionAspectSupport.java
protected void completeTransactionAfterThrowing(@Nullable TransactionInfo txInfo, Throwable ex) {
if (txInfo != null && txInfo.getTransactionStatus() != null) {
// 如果 @Transactional 配置了需要对那些异常进行回退,则需要判断抛出的异常是否匹配
// 没有配置的话只处理 RuntimeException 或者 Error 两种异常
// <1> 如果异常类型匹配成功,则进行回滚
if (txInfo.transactionAttribute != null && txInfo.transactionAttribute.rollbackOn(ex)) {
try {
// 回滚操作
// 嵌套事务,回滚到保存点;否则,新事务,回滚;否则,加入到的一个事务,设置为需要回滚
txInfo.getTransactionManager().rollback(txInfo.getTransactionStatus());
}
catch (TransactionSystemException ex2) {
logger.error("Application exception overridden by rollback exception", ex);
ex2.initApplicationException(ex);
throw ex2;
}
catch (RuntimeException | Error ex2) {
logger.error("Application exception overridden by rollback exception", ex);
throw ex2;
}
}
// <2> 否则,不需要回滚,提交事务
else {
// We don't roll back on this exception.
// Will still roll back if TransactionStatus.isRollbackOnly() is true.
try {
// 提交操作
txInfo.getTransactionManager().commit(txInfo.getTransactionStatus());
}
catch (TransactionSystemException ex2) {
logger.error("Application exception overridden by commit exception", ex);
ex2.initApplicationException(ex);
throw ex2;
}
catch (RuntimeException | Error ex2) {
logger.error("Application exception overridden by commit exception", ex);
throw ex2;
}
}
}
}
处理异常的过程如下:
- 如果异常类型匹配成功,则进行回滚,如果
@Transactional
配置了需要对哪些异常进行回退,则需要判断抛出的异常是否匹配,没有配置的话只处理 RuntimeException 或者 Error 两种异常- 回滚操作,调用
AbstractPlatformTransactionManager#rollback(TransactionStatus)
方法
- 回滚操作,调用
- 否则,异常类型不匹配
- 还是进行提交操作, 调用
AbstractPlatformTransactionManager#commit(TransactionStatus)
方法,在上面的“提交事务”小节中已经讲过
- 还是进行提交操作, 调用
可以看到,出现了异常不一定会回滚,需要异常类型匹配
3.1 rollback 方法
// AbstractPlatformTransactionManager.java
@Override
public final void rollback(TransactionStatus status) throws TransactionException {
// <1> 如果事务已完成,此时又回滚,则抛出异常
if (status.isCompleted()) {
throw new IllegalTransactionStateException(
"Transaction is already completed - do not call commit or rollback more than once per transaction");
}
DefaultTransactionStatus defStatus = (DefaultTransactionStatus) status;
// <2> 进行回滚(预料之中)
processRollback(defStatus, false);
}
3.2 processRollback 方法
// AbstractPlatformTransactionManager.java
private void processRollback(DefaultTransactionStatus status, boolean unexpected) {
try {
boolean unexpectedRollback = unexpected;
try {
// <1> 调用 TransactionSynchronization#beforeCompletion 方法
// 由 Spring 事务托管,不是真的关闭连接,从 ThreadLocal 中删除 DataSource 和 ConnectionHolder 的映射关系
// 例如在 Mybatis-Spring 中的 SqlSessionSynchronization 中,会从 ThreadLocal 中删除 SqlSessionFactory 和 SqlSessionHolder 的映射关系,
// 且调用其 SqlSession#close() 方法
triggerBeforeCompletion(status);
// <2> 如果有 Savepoint 保存点
if (status.hasSavepoint()) {
// 回滚到保存点,调用 Connection#rollback(Savepoint) 方法
status.rollbackToHeldSavepoint();
}
// <3> 否则,如果是新的事务,例如传播级别为 **REQUIRED_NEW** 则一定是一个新的事务
else if (status.isNewTransaction()) {
// 事务回滚,调用 Connection#rollback() 方法
doRollback(status);
}
// <4> 否则,不是新的事务也没有保存点,那就是加入到一个已有的事务这种情况,例如 **REQUIRED** 传播级别,如果已存在一个事务,则加入其中
else {
// Participating in larger transaction
if (status.hasTransaction()) {
// 如果已经标记为回滚,或当加入事务失败时全局回滚(默认 true)
if (status.isLocalRollbackOnly() || isGlobalRollbackOnParticipationFailure()) {
// 设置当前 ConnectionHolder#rollbackOnly 为 true
// 在这个事务提交的时候进行回滚
doSetRollbackOnly(status);
}
else { }
}
else { }
// Unexpected rollback only matters here if we're asked to fail early
// 在事务被标记为全局回滚的情况下是否提前失败
// 默认为 false,表示不希望抛出异常
if (!isFailEarlyOnGlobalRollbackOnly()) {
// 那么设置 unexpectedRollback 为 false
unexpectedRollback = false;
}
}
}
// 运行时异常或者其它异常
catch (RuntimeException | Error ex) {
triggerAfterCompletion(status, TransactionSynchronization.STATUS_UNKNOWN);
throw ex;
}
// 触发完成后的回调,事务同步状态为已提交,调用 TransactionSynchronization#afterCompletion 方法
// 例如在 Mybatis-Spring 中的 SqlSessionSynchronization 中,会从 ThreadLocal 中删除 SqlSessionFactory 和 SqlSessionHolder 的映射关系,
// 且调用其 SqlSession#close() 方法,解决可能出现的跨线程的情况
triggerAfterCompletion(status, TransactionSynchronization.STATUS_ROLLED_BACK);
// Raise UnexpectedRollbackException if we had a global rollback-only marker
// 通过上面可以看到,通常情况这里不会抛出异常
if (unexpectedRollback) {
throw new UnexpectedRollbackException(
"Transaction rolled back because it has been marked as rollback-only");
}
}
finally {
// 在完成后清理,清理相关资源,“释放”连接,唤醒被挂起的资源
cleanupAfterCompletion(status);
}
}
回退过程如下:
调用
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()
方法,如下:// DataSourceTransactionManager.java
@Override
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)事务拦截器,关于 Spring AOP 的相关内容对于不熟悉的小伙伴可查看我前面的一系列文章。
这个 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 AOP)
至此,关于 Spring IoC、Spring AOP、Spring TX 三个核心模块已经讲的差不多了,接下来我们一起来看看 Spring Boot 的相关内容
死磕Spring之AOP篇 - Spring 事务详解的更多相关文章
- 死磕Spring之AOP篇 - Spring AOP常见面试题
该系列文章是本人在学习 Spring 的过程中总结下来的,里面涉及到相关源码,可能对读者不太友好,请结合我的源码注释 Spring 源码分析 GitHub 地址 进行阅读. Spring 版本:5.1 ...
- 死磕Spring之AOP篇 - Spring AOP总览
该系列文章是本人在学习 Spring 的过程中总结下来的,里面涉及到相关源码,可能对读者不太友好,请结合我的源码注释 Spring 源码分析 GitHub 地址 进行阅读. Spring 版本:5.1 ...
- 死磕Spring之AOP篇 - Spring AOP自动代理(一)入口
该系列文章是本人在学习 Spring 的过程中总结下来的,里面涉及到相关源码,可能对读者不太友好,请结合我的源码注释 Spring 源码分析 GitHub 地址 进行阅读. Spring 版本:5.1 ...
- 死磕Spring之AOP篇 - Spring AOP自动代理(二)筛选合适的通知器
该系列文章是本人在学习 Spring 的过程中总结下来的,里面涉及到相关源码,可能对读者不太友好,请结合我的源码注释 Spring 源码分析 GitHub 地址 进行阅读. Spring 版本:5.1 ...
- 死磕Spring之AOP篇 - Spring AOP自动代理(三)创建代理对象
该系列文章是本人在学习 Spring 的过程中总结下来的,里面涉及到相关源码,可能对读者不太友好,请结合我的源码注释 Spring 源码分析 GitHub 地址 进行阅读. Spring 版本:5.1 ...
- 死磕Spring之AOP篇 - Spring AOP两种代理对象的拦截处理
该系列文章是本人在学习 Spring 的过程中总结下来的,里面涉及到相关源码,可能对读者不太友好,请结合我的源码注释 Spring 源码分析 GitHub 地址 进行阅读. Spring 版本:5.1 ...
- 死磕Spring之AOP篇 - Spring AOP注解驱动与XML配置
该系列文章是本人在学习 Spring 的过程中总结下来的,里面涉及到相关源码,可能对读者不太友好,请结合我的源码注释 Spring 源码分析 GitHub 地址 进行阅读. Spring 版本:5.1 ...
- Spring的AOP AspectJ切入点语法详解(转)
一.Spring AOP支持的AspectJ切入点指示符 切入点指示符用来指示切入点表达式目的,在Spring AOP中目前只有执行方法这一个连接点,Spring AOP支持的AspectJ切入点指示 ...
- Spring 之AOP AspectJ切入点语法详解
记录一下,以后学习 https://blog.csdn.net/zhengchao1991/article/details/53391244
随机推荐
- SpringBoot入门学习看这一篇就够了
1.SpringBoot是什么? SpringBoot是一套基于Spring框架的微服务框架. 2.为什么需要SpringBoot 由于Spring是一个轻量级的企业开发框架,主要的功能就是用于整合和 ...
- python-实现输出乘法口诀表
list1 = [1,2,3,4,5,6,7,8,9] 2 def number(num): 3 for i in list1[:num]: 4 result = 1 * i 5 print(&quo ...
- 攻防世界 reverse 进阶 -gametime
19.gametime csaw-ctf-2016-quals 这是一个小游戏,挺有意思的 's'-->' ' 'x'-->'x' 'm'-->'m' 观察流程,发现检验函 ...
- 设计vue3的请求实体工厂
设计一个vue3的请求实体工厂 目录 设计一个vue3的请求实体工厂 描述 实现 构建一个基础请求方法 创建具体请求的方法 下面是对请求的声明文件 下面是请求的定义 generateRequest对请 ...
- 学习Typora来写博客
Typora学习 标题分级 知识详解 标题分级可使用快捷键Ctry+数字键(1.2.3.4.5.6.0) 例如Ctry+1为一级标题,Ctry+2为二级标题,以此类推,总共可分为六个级别的标题,Ctr ...
- 扩展中国剩余定理(EXCRT)学习笔记
扩展中国剩余定理(EXCRT)学习笔记 用途 求解同余方程组 \(\begin{cases}x\equiv c_{1}\left( mod\ m_{1}\right) \\ x\equiv c_{2} ...
- java面试-强引用、软引用、弱引用和幻象引用有什么区别
在Java语言中,除了基本数据类型外,其他的都是指向各类对象的对象引用:Java中根据其生命周期的长短,将引用分为4类. 不同的引用类型,主要体现的是对象不同的可达性状态和对垃圾收集的影响. 1 .强 ...
- Spring Boot 2.3.0 新特性Redis 拓扑动态感应
本文为原创文章.欢迎任何形式的转载,但请务必注明出处 冷冷https://lltx.github.io. Spring Boot 2.3 新特性优雅停机详解 Spring Boot 2.3 新特性分层 ...
- FastDFS一步步搭建存储服务器
一:FastDFS简介 1:FastDFS简介 FastDFS是一个开源的轻量级分布式文件系统,功能包括:文件存储.文件同步.文件访问(文件上传.文件下载)等,解决了大容量存储和负载均衡的问题.特别适 ...
- 《疯狂Kotlin讲义》读书笔记4——流程控制
流程控制 与Java类似,Kotlin同样提供了两种基本的流程控制结构:分支结构和循环结构. Kotlin提供了 if 和 when 两种分支语句,其中 when 语句可以代替Java的switch语 ...