Spring 事务的实现原理
在执行访问数据库相关的操作中,特别是针对数据的修改操作,由于对于数据的修改可能会出现异常,因此对于整个一组的数据修改实际上都不能算是生效的,在这种情况下,需要使用事务的 “回滚” 来撤销本次执行的操作;而在执行成功之后,需要手动将这一组操作提交给数据库管理系统,使得对于数据的修改能够生效,这种操作在事务中也被称为 “提交”。
有关事务的内容可以参见:数据库事务。在实际的开发过程中,同样需要手动提交或者回滚来处理事务,这是一项十分繁琐的工作,同时,手动提交事务也不便于统一进行管理,因此一般会将事务的处理作为一个切面来进行统一的处理。在一般的场景下,都会使用 Spring 作为开发容器并通过 @Transactional
注解来对事务进行统一的管理,本文将对 Spring 中 @Transactional
的使用以及实现做简要的概述
基本使用
首先查看 @Transactional
注解对应的源代码:
package org.springframework.transaction.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.core.annotation.AliasFor;
import org.springframework.transaction.TransactionDefinition;
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Transactional {
@AliasFor("transactionManager")
String value() default "";
/**
* 这个值用于指定相关的事务管理类,可以通过 Bean 的名称来指定
*/
@AliasFor("value")
String transactionManager() default "";
/**
* 用于描述事务的相关属性
*/
String[] label() default {};
/**
* 事务的传播类型,默认为支持当前事务,如果当前事务不存在,则创建一个事务
*/
Propagation propagation() default Propagation.REQUIRED;
/**
* 事务的隔离级别,默认为 DBMS 默认的事务隔离级别
*/
Isolation isolation() default Isolation.DEFAULT;
/**
* 事务的超时时间,默认为 DBMS 的事务超时时间
*/
int timeout() default TransactionDefinition.TIMEOUT_DEFAULT;
String timeoutString() default "";
/**
* 如果事务是只读的,那么可以将这个字段设置为 true,以提高系统的性能
*/
boolean readOnly() default false;
/**
* 引起事务回滚的异常类,这个属性在自定义异常回滚时可以使用
*/
Class<? extends Throwable>[] rollbackFor() default {};
String[] rollbackForClassName() default {};
/**
* 不会导致事务回滚的异常类
*/
Class<? extends Throwable>[] noRollbackFor() default {};
String[] noRollbackForClassName() default {};
}
在使用 @Transactional
注解来自动管理事务之前,需要做开启事务管理,定义类似如下的配置 Bean,开启事务管理支持:
import org.apache.ibatis.datasource.pooled.PooledDataSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;
/**
* @author lxh
* @date 2022/7/23-下午9:24
*/
@Configuration
@EnableTransactionManagement // 开启 Spring 的事务管理
public class TransactionalConfig {
@Bean // 相关的事务管理类
public PlatformTransactionManager transactionManager() {
DataSourceTransactionManager manager = new DataSourceTransactionManager();
manager.setDataSource(new PooledDataSource("com.mysql.cj.jdbc.Driver",
"jdbc:mysql://127.0.0.1:3306/lxh", "root", "123456"));
return manager;
}
}
对于 Spring Boot 类型的项目来讲,在自动配置的过程中已经完成了相关的配置,只需加入对应的 JDBC
依赖项来是的自动配置生效即可:
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
</dependency>
在自动引入 spring-boot-starter-data*
的依赖时会包含此依赖项,因此一般情况下都不需要手动引入 JDBC
的依赖
当做了上面的一些配置之后,现在就可以使用 Spring 的 @Transactional
注解来使得事务生效,例如,对于下面的 Service
类,现在 Spring 就会自动生成对应的代理类来实现相关的事务行为:
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.xhliu.demo.entity.UserInfo;
import org.xhliu.demo.mapper.UserInfoMapper;
import org.xhliu.demo.service.UserService;
import javax.annotation.Resource;
/**
* @author lxh
* @date 2022/7/21-下午11:02
*/
@Service
@Transactional(rollbackFor = Exception.class)
public class UserServiceImpl implements UserService {
@Resource
private UserInfoMapper userInfoMapper;
@Override
public void initUserInfos() {
UserInfo userInfo = userInfoMapper.selectByUserName("yyf");
if (userInfo == null) {
UserInfo valObj = new UserInfo();
valObj.setUserName("yyf");
valObj.setUserAge(23);
valObj.setDescribe("");
userInfoMapper.insertUserInfo(valObj);
}
}
}
由于现在的 @Transactional
注解加在类上,因此对于当前类的所有 public
修饰的方法都会具有相关的事务行为。
值得一提的是,@Transactional
注解不仅仅可以添加在类上,而且可以加在接口、类或者类的相关方法上,当 @Transactional
在多个地方同时定义时,会按照以下的优先级进行处理(高—>低):接口(类级别)、父类(类级别)、类(类级别)、接口内方法(方法级别)、父类方法(方法级别)以及本类方法(方法级别)
实现原理
根据 @Transactional
注解存在的行为,可以简单的推断一下实现的原理,很明显,这是在执行前后添加了对应的逻辑。而在 Java 中实现这样的功能也被成为 AOP
(面向切面编程),实现 AOP
的方式目前就两种主流的方式:AspectJ 和代理。结合 Spring 中对于 AOP 的实现,可以大致推断出实现方式为代理的实现方式
相关 Bean 的加载
结合 Spring Boot 自动装载 Bean 的流程可以发现,对于事务的处理都是在 org.springframework.boot.autoconfigure.jdbc.TransactionAutoConfiguration
配置中中完成自动装配。比较关键的代码如下:
@Configuration(proxyBeanMethods = false)
@ConditionalOnBean(TransactionManager.class) // 只有当 Bean 容器中存在 TransactionManager 类型的 Bean 时才加载下面的 Bean
// 只有当 Bean 容器中不存在 AbstractTransactionManagementConfiguration 类型的 Bean 时才进行后续 Bean 的加载
@ConditionalOnMissingBean(AbstractTransactionManagementConfiguration.class)
public static class EnableTransactionManagementConfiguration {
@Configuration(proxyBeanMethods = false)
@EnableTransactionManagement(proxyTargetClass = false)
@ConditionalOnProperty(prefix = "spring.aop", name = "proxy-target-class", havingValue = "false")
public static class JdkDynamicAutoProxyConfiguration {
}
@Configuration(proxyBeanMethods = false)
@EnableTransactionManagement(proxyTargetClass = true)
@ConditionalOnProperty(prefix = "spring.aop", name = "proxy-target-class", havingValue = "true",
matchIfMissing = true)
public static class CglibAutoProxyConfiguration {
}
}
Note:在这里 TransactionManager
类型的 Bean 在实际的使用场景中,一般是 org.springframework.jdbc.support.JdbcTransactionManager
,而 AbstractTransactionManagementConfiguration
的具体实现类在一般 JDBC
场景下的具体实现类为 org.springframework.transaction.annotation.ProxyTransactionManagementConfiguration
在 ProxyTransactionManagementConfiguration
配置类中,最为核心的部分是有关切面的定义,具体代码如下:
@Bean(name = TransactionManagementConfigUtils.TRANSACTION_ADVISOR_BEAN_NAME)
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public BeanFactoryTransactionAttributeSourceAdvisor transactionAdvisor(
TransactionAttributeSource transactionAttributeSource, TransactionInterceptor transactionInterceptor) {
BeanFactoryTransactionAttributeSourceAdvisor advisor = new BeanFactoryTransactionAttributeSourceAdvisor();
advisor.setTransactionAttributeSource(transactionAttributeSource);
advisor.setAdvice(transactionInterceptor);
if (this.enableTx != null) {
advisor.setOrder(this.enableTx.<Integer>getNumber("order"));
}
return advisor;
}
/**
* 由于注入时会考虑 Bean 的名称,下面的两个 Bean 将会分别注入到上面对应的参数中
*/
@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public TransactionAttributeSource transactionAttributeSource() {
return new AnnotationTransactionAttributeSource();
}
@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE) // 对应的拦截器,定义了具体的处理逻辑
public TransactionInterceptor transactionInterceptor(TransactionAttributeSource transactionAttributeSource) {
TransactionInterceptor interceptor = new TransactionInterceptor();
interceptor.setTransactionAttributeSource(transactionAttributeSource);
if (this.txManager != null) {
interceptor.setTransactionManager(this.txManager);
}
return interceptor;
}
@Transactional
注解属性的处理
注解的目的是用于提供相关的元数据信息,这些元数据信息可以保留到运行时。注解本身不具备任何业务逻辑,所有的业务逻辑都需要对应的业务代码进行处理。在当前的 JDBC
的环境下,对于 @Transactional
注解来讲,结合上文的参数 Bean,可以看到 org.springframework.transaction.annotation.AnnotationTransactionAttributeSource
的实例化逻辑:
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 {
// 因此最后的注解处理类就是 SpringTransactionAnnotationParser
this.annotationParsers = Collections.singleton(new SpringTransactionAnnotationParser());
}
}
可以看到,最终在 AnnotationTransactionAttributeSource
中对于 @Transacational
注解的处理是通过 SpringTransactionAnnotationParser
来进行解析,查看对应解析 @Transactional
注解元数据的源码如下:
@Override
@Nullable
public TransactionAttribute parseTransactionAnnotation(AnnotatedElement element) {
AnnotationAttributes attributes = AnnotatedElementUtils.findMergedAnnotationAttributes(
element, Transactional.class, false, false);
if (attributes != null) {
// 在这里完成 @Transactional 注解的元数据的解析,其实就是获取注解的方法的返回值而已
return parseTransactionAnnotation(attributes);
}
else {
return null;
}
}
代理对象的实例化
回想一下 Spring 中 Bean 的生命周期,在较低版本的 Spring 中,为了处理循环依赖,对于代理对象的处理在 org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory
的 getEarlyBeanReference
方法处理对应的依赖项,处理依赖的同时也会创建对应的代理对象实例(对非依赖项将会在初始化 Bean 时对处理逻辑进行代理,但是逻辑是相同的),具体的源码如下:
protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
Object exposedObject = bean;
if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
for (SmartInstantiationAwareBeanPostProcessor bp : getBeanPostProcessorCache().smartInstantiationAware) {
exposedObject = bp.getEarlyBeanReference(exposedObject, beanName);
}
}
return exposedObject;
}
实际上创建代理的 SmartInstantiationAwareBeanPostProcessor
为 AbstractAutoProxyCreator
,对应的逻辑如下:
public Object getEarlyBeanReference(Object bean, String beanName) {
Object cacheKey = getCacheKey(bean.getClass(), beanName);
this.earlyProxyReferences.put(cacheKey, bean);
return wrapIfNecessary(bean, beanName, cacheKey); // 对目标类进行封装,得到实际的代理对象
}
protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
// 省略部分代码
// 根据当前对象的类型,检查能够适应到的拦截方法,从而创建对应的代理对象
Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);
if (specificInterceptors != DO_NOT_PROXY) {
this.advisedBeans.put(cacheKey, Boolean.TRUE);
Object proxy = createProxy(
bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));
this.proxyTypes.put(cacheKey, proxy.getClass());
return proxy;
}
this.advisedBeans.put(cacheKey, Boolean.FALSE);
return bean;
}
事务的处理逻辑
前文我们提到过,在 BeanFactoryTransactionAttributeSourceAdvisor
切面中定义的拦截器为 TransactionInterceptor
,因此在创建对应的代理类时会将对应的处理逻辑加入到生成的代理类中,而在 TransactionInterceptor
中定义的处理逻辑如下:
// invoke 方法可以说是代理中的熟客了
public Object invoke(MethodInvocation invocation) throws Throwable {
// Work out the target class: may be {@code null}.
// The TransactionAttributeSource should be passed the target class
// as well as the method, which may be from an interface.
Class<?> targetClass = (invocation.getThis() != null ? AopUtils.getTargetClass(invocation.getThis()) : null);
// Adapt to TransactionAspectSupport's invokeWithinTransaction...
return invokeWithinTransaction(invocation.getMethod(), targetClass, new CoroutinesInvocationCallback() {
@Override
@Nullable
public Object proceedWithInvocation() throws Throwable {
return invocation.proceed();
}
@Override
public Object getTarget() {
return invocation.getThis();
}
@Override
public Object[] getArguments() {
return invocation.getArguments();
}
});
}
继续向下跟踪处理逻辑,可以看到在 org.springframework.transaction.interceptor.TransactionAspectSupport
的 invokeWithinTransaction
中定义了事务处理的相关逻辑:
protected Object invokeWithinTransaction(Method method, @Nullable Class<?> targetClass,
final InvocationCallback invocation) throws Throwable {
TransactionAttributeSource tas = getTransactionAttributeSource();
final TransactionAttribute txAttr = (tas != null ? tas.getTransactionAttribute(method, targetClass) : null);
final TransactionManager tm = determineTransactionManager(txAttr);
// 省略 Reactive 中相关的事务处理逻辑
PlatformTransactionManager ptm = asPlatformTransactionManager(tm);
final String joinpointIdentification = methodIdentification(method, targetClass, txAttr);
// 这里的 ptm 在 JDBC 环境下为 JdbcTransactionManager,因此实际上这里就是对应的事务处理的相关逻辑
if (txAttr == null || !(ptm instanceof CallbackPreferringPlatformTransactionManager)) {
/*
根据 @Transactional 中 propagation 属性开启事务(其实只是创建了一个对 TransactionManager 的引用对象而已,实际的
事务管理最终都需要通过 TransationManager 对象来进行处理)
*/
TransactionInfo txInfo = createTransactionIfNecessary(ptm, txAttr, joinpointIdentification);
Object retVal;
try {
retVal = invocation.proceedWithInvocation();
}
catch (Throwable ex) {
// 事务的回滚处理
completeTransactionAfterThrowing(txInfo, ex);
throw ex;
}
finally {
cleanupTransactionInfo(txInfo);
}
if (retVal != null && vavrPresent && VavrDelegate.isVavrTry(retVal)) {
// Set rollback-only in case of Vavr failure matching our rollback rules...
TransactionStatus status = txInfo.getTransactionStatus();
if (status != null && txAttr != null) {
retVal = VavrDelegate.evaluateTryFailure(retVal, txAttr, status);
}
}
commitTransactionAfterReturning(txInfo); // 提交事务
return retVal;
}
// 省略部分代码。。。。
}
实际场景中遇到的一些问题
注解作用于方法
由于 Spring 对于
@Transactional
注解的实际事务实现是通过代理的方式来实现的,那么这样做可能会存在以下一些问题:对于非
public
或protected
修饰符修饰的方法,事务是失效的,而对于protected
修饰的方法,当使用 JDK 的动态代理时,也会导致失效。这是因为对于代理的实现来讲,不管是何种动态代理的实现,都要至少保证能够访问到对应的方法对于方法的内部方法调用,那么即使内部方法是通过
public
修饰符修饰的,但是在此时调用它的方法中依旧是事务失效的。以下图为例:由于被代理的方法
MethodA
在被代理时,内部调用的MethodB
依旧是原有对象中的MethodB
,因此对于MethodB
的代理结果将不会反映到MethodA
中对于MethodB
的调用中
事务的回滚失效
结合对应的源码,可以分析一下造成事务回滚失效的可能原因:事务的代理没有生效?有没有达到触发回滚的条件?如果是代理没有生效,那么就需要检查一下代理的方法是否居于可访问的权限,以及访问的方法确实是被代理的方法。如果是后者的原因,那么可以检查一下对应的
@Transactional
属性设置的相关值,如果抛出的异常不是会导致回滚的异常,那么此时的回滚必然是失效的;而更加一般的情况时,大多数人会尝试使用try{}catch{}
来捕获异常,使得代理的逻辑无法接收到异常,从而导致事务回滚失效
Spring 事务的实现原理的更多相关文章
- spring事务管理实现原理-源码-传播属性
转载请标识 https://me.csdn.net/wanghaitao4j https://blog.csdn.net/wanghaitao4j/article/details/83625260 本 ...
- Spring事务的底层原理
1. 划分处理单元--IOC 由于spring解决的问题是对单个数据库进行局部事务处理的,具体的实现首相用spring 中的IOC划分了事务处理单元.并且将对事务的各种配置放到了ioc容器中(设置事务 ...
- Spring事务用法示例与实现原理
关于Java中的事务,简单来说,就是为了保证数据完整性而存在的一种工具,其主要有四大特性:原子性,一致性,隔离性和持久性.对于Spring事务,其最终还是在数据库层面实现的,而Spring只是以一种比 ...
- Spring事务原理一探
概括来讲,事务是一个由有限操作集合组成的逻辑单元.事务操作包含两个目的,数据 一致以及操作隔离.数据一致是指事务提交时保证事务内的所有操作都成功完成,并且 更改永久生效:事务回滚时,保证能够恢复到事务 ...
- 【Spring系列】- Spring事务底层原理
Spring事务底层原理 生命不息,写作不止 继续踏上学习之路,学之分享笔记 总有一天我也能像各位大佬一样 一个有梦有戏的人 @怒放吧德德 分享学习心得,欢迎指正,大家一起学习成长! 目录 Sprin ...
- Spring事务管理源码分析
Spring事务管理方式 依据Spring.xsd文件可以发现,Spring提供了advice,annotation-driven,jta-transaction-manager3种事务管理方式.详情 ...
- Spring事务源码阅读笔记
1. 背景 本文主要介绍Spring声明式事务的实现原理及源码.对一些工作中的案例与事务源码中的参数进行总结. 2. 基本概念 2.1 基本名词解释 名词 概念 PlatformTransaction ...
- Spring事务实现分析
一.Spring声明式事务用法 1.在spring配置文件中配置事务管理器 <bean id="baseDataSource" class="com.alibaba ...
- Spring 事务管理原理探究
此处先粘贴出Spring事务需要的配置内容: 1.Spring事务管理器的配置文件: 2.一个普通的JPA框架(此处是mybatis)的配置文件: <bean id="sqlSessi ...
- 深入理解 Spring 事务原理
本文由码农网 – 吴极心原创,转载请看清文末的转载要求,欢迎参与我们的付费投稿计划! 一.事务的基本原理 Spring事务的本质其实就是数据库对事务的支持,没有数据库的事务支持,spring是无法提供 ...
随机推荐
- .NET周刊【9月第3期 2023-09-17】
国内文章 在.NET 8 RC1 版本中 MAUI.ASP.NET Core 和 EF8 的新特性 https://www.cnblogs.com/shanyou/p/17698428.html 从年 ...
- Java11配置maven
这里假设Java11和maven都正确安装,使用的版本为Java11.maven3.6.1 测试环境变量 Java win + r 打开运行,输入 cmd,打开命令行提示符,输入java --vers ...
- 在 Rust 中实现 Repository 仓储模式
前言 单位上有个 Rust 项目,orm 选型很长时间都没定下来,故先设计了抽象的仓储层方便写业务逻辑. 设计抽象接口 抽象只读接口,仅读取使用,目前需求仅用查询 id.查询全部和按名称搜索,当然理应 ...
- 《最新出炉》系列初窥篇-Python+Playwright自动化测试-20-处理鼠标拖拽-下篇
1.简介 上一篇中,宏哥说的宏哥在最后提到网站的反爬虫机制,那么宏哥在自己本地做一个网页,没有那个反爬虫的机制,谷歌浏览器是不是就可以验证成功了,宏哥就想验证一下自己想法,其次有人私信宏哥说是有那种类 ...
- python-微信
wxpy/itchat已禁用 自从微信禁止网页版登陆之后,itchat 库实现的功能也就都不能用了: itchat现在叫wxpy 1.安装库wxpy: PS D:\01VSCodeScript\Pyt ...
- 从零用VitePress搭建博客教程(3) - VitePress页脚、标题logo、最后更新时间等相关细节配置
接上一节:从零用VitePress搭建博客教程(2) –VitePress默认首页和头部导航.左侧导航配置 五.默认主题相关细节配置 关于默认主题的标题,logo.页脚,最后更新时间等相关细节配置,我 ...
- hammer.js学习
demo:https://github.com/fei1314/HammerJs/tree/master 知识点: hammer--手势识别:点击.长按.滑动.拖动.旋转.缩放 方法: tap 快速的 ...
- mac os 升级到13后,系统免密失败
# sudo vim /etc/ssh/ssh_config # 添加以下内容 PubkeyAcceptedKeyTypes +ssh-rsa
- SNN_文献阅读_Recent Advances and New Frontiers in Spiking Neural Networks
Recent Advances and New Frontiers in Spiking Neural Networks 基本要素:包括神经元模型.神经元中脉冲序列的编码方法.神经网络中每个基本层的拓 ...
- 题解 CF690C1
题目大意: 给定一张 \(n\) 个点 \(m\) 条边的无向图,判断这是不是一棵树. 题目分析: 两种思路: 思路一: 不需要建图,直接使用并查集判环即可 最后判断一下图联不联通就行,具体方法就是看 ...