浅析Spring事务实现原理
SQL事务实现简介
首先我们来了解下,最简单的事务是怎么实现的呢?以JDBC为例,当一个数据库Connection对象创建后,其会默认自动提交事务;每次执行SQL语句时,如果成功,就会向数据库自动提交,不能回滚。
通过调用setAutoCommit(false)方法可以取消自动提交事务。等到所有的SQL语句都执行成功后,调用commit()方法提交事务。如果其中某个操作失败或出现异常时,则调用rollback()方法回滚事务。具体代码如下所示:
public void noTransaction() {Connection connection = null;String sql = "update account set balance=balance-100 where id=1";String sql2 = "update account set balance=balance+100 where id=2";//创建PreparedStatement对象PreparedStatement preparedStatement = null;try {connection = JDBCUtils.getConnection();// 获取数据库连接connection.setAutoCommit(false);//事务开始preparedStatement = connection.prepareStatement(sql);preparedStatement.executeUpdate();//执行第一个sqlpreparedStatement = connection.prepareStatement(sql2);preparedStatement.executeUpdate();//执行sql2//提交事务connection.commit();} catch (SQLException e) {//进行事务回滚,默认回滚到事务开始的地方try {connection.rollback();} catch (SQLException ex) {ex.printStackTrace();}e.printStackTrace();} finally {//关闭流JDBCUtils.close(null, preparedStatement, connection);}}
复制代码
将代码抽象成执行步骤,主要有以下四步:
- 获取Mysql链接
- 执行SQL语句
- 提交SQL事务
- 存在异常则做Mysql的事务回滚。
可以发现,常规情况下只有执行SQL语句的内容存在差异。如果能将相同部分抽取出来,接入方接入时只考虑SQL语句内容,就可以减少接入的成本。同时观察到抽取的部分处于执行SQL语句的前后,那么很自然的就可以想到两种解决方案:
1、在JAVA8中,提供了函数式编程。我们可以将要执行的SQL语句封装成函数,作为入参传入并执行。
2、采用动态代理对执行SQL的前后做增强。
编程式事务
Spring中采用函数式编程实现的事务,被称为编程式事务。编程式事务的实现相对简单,主要由类TransactionTemplate负责实现。具体代码可以见如下所示:
@Override
@Nullable
public <T> T execute(TransactionCallback<T> action) throws TransactionException {Assert.state(this.transactionManager != null, "No PlatformTransactionManager set");if (this.transactionManager instanceof CallbackPreferringPlatformTransactionManager) {return ((CallbackPreferringPlatformTransactionManager) this.transactionManager).execute(this, action);}else {//获取事务 TransactionStatus status = this.transactionManager.getTransaction(this);T result;try {//执行SQL语句内容result = action.doInTransaction(status);}catch (RuntimeException | Error ex) {//异常回滚rollbackOnException(status, ex);throw ex;}catch (Throwable ex) {// 异常回滚rollbackOnException(status, ex);throw new UndeclaredThrowableException(ex, "TransactionCallback threw undeclared checked exception");}//提交事务this.transactionManager.commit(status);return result;}
}
复制代码
TransactionCallBack作为入参传入,其中就主要是我们要执行的SQL语句内容。而其余部分可以看到,其实就和我们前面所描述的四步基本相似:
- 获取Mysql链接
- 执行SQL语句
- 提交SQL事务
- 存在异常则做Mysql的事务回滚。
声明式事务
在Spring中,采用AOP做增强逻辑的被称为声明式事务。相比起编程式事务,声明式事务相对复杂。因此,在了解声明式事务之前,我们需要先简单了解一下Spring是如何支持AOP(动态代理)。首先我们知道,Spring中Bean的存在形式有以下几个阶段:
其中非常关键点就在BeanFactory。当我们对一个Bean定义代理对象后,BeanFactory生成的就不会是单纯的Bean实例对象,而是Bean的动态代理。通过调用Bean的动态代理中的方法,来实现AOP。那么如何自定义自己的AOP呢?要实现AOP需要明确两个点:
1、需要在哪里做增强?(定义切点)
2、需要做什么样的增强逻辑?(定义增强逻辑)
对于这两点,Spring主要通过**事务代理管理配置类(ProxyTransactionManagementConfiguration)**进行实现。
从类图中可以看到,事务代理管理配置类主要定义了三个Bean对象:
- 注释事务属性源(AnnotationTransactionAttributeSource),其主要负责判断当前类是否为需要增强的类,即"哪里需要做增强"。
- 事务拦截器(TransactionInterceptor),该类主要负责对事务做链接获取、事务提交以及事务回滚。即"怎么做增强"。
- Bean工厂事务属性源指导(BeanFactoryTransactionAttributeSourceAdvisor),这个与事务本身无关,主要是在Bean工厂生产Bean实例的时候,方便对Bean进行替换使用的。其中主要是负责将定义的切点和增强逻辑注入到Spring中。
这里我们逐一来介绍这三个Bean对象。
注释事务属性源
"哪里需要做增强",意味着类要具备判断是否需增强的能力。为此,注释事务属性源提供了一个关键的方法:isCandidateClass()。
但声明事务的注解一定不只一种。如果需要识别所有包下的事务型注解,一定会需要多次判断。因此,在注解事务属性源中,还保存了一组接口对象事务注释解析器(TransactionAnnotationParser),通过循环遍历这组事务注释解析器,就可以对不同框架注解进行处理。具体源码如下:
@Override
public boolean isCandidateClass(Class<?> targetClass) {for (TransactionAnnotationParser parser : this.annotationParsers) {if (parser.isCandidateClass(targetClass)) {return true;}}return false;
}
复制代码
以SpringTransactionAnnotationParser注释解析器为例,其实现的isCandidateClass()方法判断类是否被@Transactional类注释了,如果是,那么该类就是潜在的候选类。
@Override
public boolean isCandidateClass(Class<?> targetClass) {return AnnotationUtils.isCandidateClass(targetClass, Transactional.class);
}
复制代码
依次类推,对@TransactionAttribute等其他框架的注释,我们都可以采用这样方法实现。
事务拦截器
具备了判断哪些类需要执行事务的能力后,我们还需要确定具体的增强逻辑是什么样子的。而这就是事务拦截器主要功能。要实现这个功能,需要在对应方法被调用时,执行增强方法。
从类图首先可以看到,为了能够察觉到方法的调用,事务拦截器实现了方法拦截器接口(MethodInterceptor)的invoke方法,在invoke方法中先判断当前执行的方法属于哪个类,紧接着会用invokeWithinTransaction()对方法进行事务性的包装。其源码如下:
@Override
@Nullable
public Object invoke(MethodInvocation invocation) throws Throwable {// 判断执行的方法属于哪个类Class<?> targetClass = (invocation.getThis() != null ? AopUtils.getTargetClass(invocation.getThis()) : null);//再调用事务进行执行return invokeWithinTransaction(invocation.getMethod(), targetClass, new CoroutinesInvocationCallback() {@Override@Nullablepublic Object proceedWithInvocation() throws Throwable {return invocation.proceed();}@Overridepublic Object getTarget() {return invocation.getThis();}@Overridepublic Object[] getArguments() {return invocation.getArguments();}});
}
复制代码
主要逻辑放在invokeWithinTransaction()方法中。在该方法中,主要考虑了三类不同的编程方式的事务,分别是:响应式事务(ReactiveTransactionManager)、回调优先型事务(CallbackPreferringPlatformTransactionManager)和非回调优先型事务(非CallbackPreferringPlatformTransactionManager)。
三者的差异主要在于:
1、响应式编程常采用Mono或Flux实现,需要对两种方式选择相应适配器做适配。
2、后两者从名字上可以看出差异,回调型优先的事务,会先执行回调再执行事务。而非回调优先型事务,则关注于事务的执行,至于回调的失败与否不需要影响事务的回滚。
尽管三者存在一些差异,但他们对于事务的实现其实是相似的,这里以非回调优先型事务为例子:
@Nullable
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);.......PlatformTransactionManager ptm = asPlatformTransactionManager(tm);final String joinpointIdentification = methodIdentification(method, targetClass, txAttr);if (txAttr == null || !(ptm instanceof CallbackPreferringPlatformTransactionManager)) {// 创建事务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)) {TransactionStatus status = txInfo.getTransactionStatus();if (status != null && txAttr != null) {retVal = VavrDelegate.evaluateTryFailure(retVal, txAttr, status);}}// 提交事务commitTransactionAfterReturning(txInfo);return retVal;}
}
复制代码
源码本身不复杂,可以看到也是四步:
- 获取Mysql链接信息
- 执行SQL语句
- 提交SQL事务
- 存在异常则做Mysql的事务回滚。
Bean工厂事务属性源指导
对于Bean工厂事务属性源指导,其主要负责用于定义切点和增强逻辑,并将这些事务的逻辑注册到Spring中用于实现。如下是Bean工厂事务属性源指导的类图。
从类图上可以看到,其继承了AbstractPointcutAdvisor关键模版类,该类是Spring中用于定义切点和增强逻辑。通过指定PointCut和Advice,就可以实现自定义的增强逻辑。因此,Bean工厂事务属性源指导只要将事务拦截器标记为增强逻辑,将注释事务属性源标记为切点,就可以让其在Spring中作为AOP生效。
通过这三者的合作:注释事务属性源标注了切点(说明我那些方法需要做增强);事务拦截器定义了要执行的增强逻辑(说明我对这些方法怎么做增强);Bean工厂事务属性源指导则将切点和增强逻辑注入到Spring中使其生效。从而实现了Spring的声明式事务的内容。
事务多样性支持
在前述内容中,我们思考了SQL情况下如何实现事务。但有个问题,如果数据源换成Redission、换成分布式事务的API,代码还能快速复用么?简而言之,Spring是如何支持数据源多样性?如何确保新数据源的快速接入?
对实现事务的流程做进一步抽象,不难发现一次事务中,框架需要关注的功能其实只有三个:
- 获取事务链接
- 提交事务
- 事务回滚
因此,对不同的数据源,都可以将其抽象成这三个能力。应用层只需要对这三个能力进行调用,就不会在因为下层数据源的差异而需要大幅度的改动。而这正与面向接口设计的思想不谋而合
为此,Spring专门设计了接口PlatformTransactionManager,其主要负责对外提供三个方法:getTransaction(definition)、commit(status)、rollback(status)。就用来抽象的上述的三个功能。由此一来,应用层的代码实现类(这里以TransactionTemplate为例子)就不再需要依赖于我的数据源究竟是JDBC、Redission还是DataSource。面对抽象编程,从而减少了接入需要考虑不同类型所带来的成本。
总结
本文介绍了Spring中针对SQL事务实现的两种方式:编程式事务和声明式事务。同时介绍了对于多种不同的数据源,Spring在设计上的架构实现,希望对大家后续的开发设计有所帮助。
浅析Spring事务实现原理相关推荐
- 【JAVA SE】第十七章 反射、注解与Spring事务底层原理
第十七章 反射.注解与Spring事务底层原理 文章目录 第十七章 反射.注解与Spring事务底层原理 一.反射 1.简介 2.意义 3.缺点 4.应用场景 5.反射技术的使用 二.注解 1.概念 ...
- 分析Spring事务管理原理及应用
目录 一.Spring事务管理介绍 (一)基本理论 (二)实际工作中的举例 (三)简单应用举例 二.Spring事务配置介绍 (一)Spring事务属性介绍 传播属性(传播行为)可选值说明 (二)声明 ...
- 浅析Spring 事务(十九) 简介事务
经过前面十八个章节的浅析,我们已经初步了解了Spring框架的左膀右臂IoC和AOP,今天我们开始一起分析一下Spring的事务~ 首先,今天算是开篇介绍~先说一下,什么是事务,事务是数据库的比较特有 ...
- 浅析Spring事务传播行为和隔离级别
7个传播行为.4个隔离级别. Spring事务的传播行为和隔离级别[transaction behaviorand isolatedlevel] Spring中事务的定义: Propagation(k ...
- Spring 事务传播原理及数据库事务操作原理
相关内容: 架构师系列内容:架构师学习笔记(持续更新) 先看看 Spring 事务的基础配置 <beans xmlns="http://www.springframework.org/ ...
- Spring 事务底层原理
一.声明式事务: /** * 声明式事务: * * 环境搭建: * 1.导入相关依赖 * 数据源.数据库驱动.Spring-jdbc模块 * 2.配置数据源.JdbcTemplate(Spring提供 ...
- Spring事务实现原理
对于一个应用而言,事务的使用基本是不可避免的.虽然Spring给我们提供了开箱即用的事务功能-- @Transactional . 但是,自带的事务功能却也存在控制粒度不够的缺点.更糟糕的是, @Tr ...
- Spring 事务底层原理,你会了吗?
点击上方 好好学java ,选择 星标 公众号 重磅资讯.干货,第一时间送达 今日推荐:八个开源的 Spring Boot 学习资源,你值得拥有个人原创+1博客:点击前往,查看更多 作者:香沙小熊 链 ...
- Spring事务切面原理
一.前言 本节我们来谈谈 <tx:advice/>.<aop:config> 标签如何创建事务切面的. 二.<tx:advice/>.<aop:config& ...
最新文章
- 目标检测coco数据集点滴介绍
- 重磅 | 2018年清华大学研究生新生大数据
- 谁是李党生?带领中国本土期刊登上国际C位,施一公说他眼光很毒辣
- Android系统的进程分类
- 数控程序中r及q代表什么_邹军:如何利用数学公式编写cnc程序?
- 从FM推演各深度学习CTR预估模型
- qml c++函数 slot_浅析Qt(C++),QML与HTML之间的交互
- shell编程中if []的用法注意
- HGOI 20190816 省常中互测8
- C++简介(5)STL
- Java SE书籍推荐
- 【FPGA】 Altera FPGA 入门篇(1)
- 利用 QTcpSocket 实现的进程间通信
- Mapper和dao
- 文科女生转行软件测试之路
- 华为ensp ospf综合实验
- 读书笔记--交流电的瞬时值和有效值
- 富士康Java开发面试题目
- 省社科基金本子评审标准总结
- [Tree Breadth First Search] 二叉树的最大深度