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);}}
复制代码

​ 将代码抽象成执行步骤,主要有以下四步:

  1. 获取Mysql链接
  2. 执行SQL语句
  3. 提交SQL事务
  4. 存在异常则做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语句内容。而其余部分可以看到,其实就和我们前面所描述的四步基本相似:

  1. 获取Mysql链接
  2. 执行SQL语句
  3. 提交SQL事务
  4. 存在异常则做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;}
}
复制代码

​ 源码本身不复杂,可以看到也是四步:

  1. 获取Mysql链接信息
  2. 执行SQL语句
  3. 提交SQL事务
  4. 存在异常则做Mysql的事务回滚。

Bean工厂事务属性源指导

​ 对于Bean工厂事务属性源指导,其主要负责用于定义切点和增强逻辑,并将这些事务的逻辑注册到Spring中用于实现。如下是Bean工厂事务属性源指导的类图。

​ 从类图上可以看到,其继承了AbstractPointcutAdvisor关键模版类,该类是Spring中用于定义切点和增强逻辑。通过指定PointCut和Advice,就可以实现自定义的增强逻辑。因此,Bean工厂事务属性源指导只要将事务拦截器标记为增强逻辑,将注释事务属性源标记为切点,就可以让其在Spring中作为AOP生效。

​ 通过这三者的合作:注释事务属性源标注了切点(说明我那些方法需要做增强);事务拦截器定义了要执行的增强逻辑(说明我对这些方法怎么做增强);Bean工厂事务属性源指导则将切点和增强逻辑注入到Spring中使其生效。从而实现了Spring的声明式事务的内容。

事务多样性支持

​ 在前述内容中,我们思考了SQL情况下如何实现事务。但有个问题,如果数据源换成Redission、换成分布式事务的API,代码还能快速复用么?简而言之,Spring是如何支持数据源多样性?如何确保新数据源的快速接入?

​ 对实现事务的流程做进一步抽象,不难发现一次事务中,框架需要关注的功能其实只有三个:

  1. 获取事务链接
  2. 提交事务
  3. 事务回滚

​ 因此,对不同的数据源,都可以将其抽象成这三个能力。应用层只需要对这三个能力进行调用,就不会在因为下层数据源的差异而需要大幅度的改动。而这正与面向接口设计的思想不谋而合

​ 为此,Spring专门设计了接口PlatformTransactionManager,其主要负责对外提供三个方法:getTransaction(definition)、commit(status)、rollback(status)。就用来抽象的上述的三个功能。由此一来,应用层的代码实现类(这里以TransactionTemplate为例子)就不再需要依赖于我的数据源究竟是JDBC、Redission还是DataSource。面对抽象编程,从而减少了接入需要考虑不同类型所带来的成本。

总结

​ 本文介绍了Spring中针对SQL事务实现的两种方式:编程式事务声明式事务。同时介绍了对于多种不同的数据源,Spring在设计上的架构实现,希望对大家后续的开发设计有所帮助。

浅析Spring事务实现原理相关推荐

  1. 【JAVA SE】第十七章 反射、注解与Spring事务底层原理

    第十七章 反射.注解与Spring事务底层原理 文章目录 第十七章 反射.注解与Spring事务底层原理 一.反射 1.简介 2.意义 3.缺点 4.应用场景 5.反射技术的使用 二.注解 1.概念 ...

  2. 分析Spring事务管理原理及应用

    目录 一.Spring事务管理介绍 (一)基本理论 (二)实际工作中的举例 (三)简单应用举例 二.Spring事务配置介绍 (一)Spring事务属性介绍 传播属性(传播行为)可选值说明 (二)声明 ...

  3. 浅析Spring 事务(十九) 简介事务

    经过前面十八个章节的浅析,我们已经初步了解了Spring框架的左膀右臂IoC和AOP,今天我们开始一起分析一下Spring的事务~ 首先,今天算是开篇介绍~先说一下,什么是事务,事务是数据库的比较特有 ...

  4. 浅析Spring事务传播行为和隔离级别

    7个传播行为.4个隔离级别. Spring事务的传播行为和隔离级别[transaction behaviorand isolatedlevel] Spring中事务的定义: Propagation(k ...

  5. Spring 事务传播原理及数据库事务操作原理

    相关内容: 架构师系列内容:架构师学习笔记(持续更新) 先看看 Spring 事务的基础配置 <beans xmlns="http://www.springframework.org/ ...

  6. Spring 事务底层原理

    一.声明式事务: /** * 声明式事务: * * 环境搭建: * 1.导入相关依赖 * 数据源.数据库驱动.Spring-jdbc模块 * 2.配置数据源.JdbcTemplate(Spring提供 ...

  7. Spring事务实现原理

    对于一个应用而言,事务的使用基本是不可避免的.虽然Spring给我们提供了开箱即用的事务功能-- @Transactional . 但是,自带的事务功能却也存在控制粒度不够的缺点.更糟糕的是, @Tr ...

  8. Spring 事务底层原理,你会了吗?

    点击上方 好好学java ,选择 星标 公众号 重磅资讯.干货,第一时间送达 今日推荐:八个开源的 Spring Boot 学习资源,你值得拥有个人原创+1博客:点击前往,查看更多 作者:香沙小熊 链 ...

  9. Spring事务切面原理

    一.前言 本节我们来谈谈 <tx:advice/>.<aop:config> 标签如何创建事务切面的. 二.<tx:advice/>.<aop:config& ...

最新文章

  1. 目标检测coco数据集点滴介绍
  2. 重磅 | 2018年清华大学研究生新生大数据
  3. 谁是李党生?带领中国本土期刊登上国际C位,施一公说他眼光很毒辣
  4. Android系统的进程分类
  5. 数控程序中r及q代表什么_邹军:如何利用数学公式编写cnc程序?
  6. 从FM推演各深度学习CTR预估模型
  7. qml c++函数 slot_浅析Qt(C++),QML与HTML之间的交互
  8. shell编程中if []的用法注意
  9. HGOI 20190816 省常中互测8
  10. C++简介(5)STL
  11. Java SE书籍推荐
  12. 【FPGA】 Altera FPGA 入门篇(1)
  13. 利用 QTcpSocket 实现的进程间通信
  14. Mapper和dao
  15. 文科女生转行软件测试之路
  16. 华为ensp ospf综合实验
  17. 读书笔记--交流电的瞬时值和有效值
  18. 富士康Java开发面试题目
  19. 省社科基金本子评审标准总结
  20. [Tree Breadth First Search] 二叉树的最大深度

热门文章

  1. 三谈大数据之足球盘口赔率水位分析思路及其实现利器
  2. js 字符串去除收尾空格
  3. c语言打印函数的使用
  4. C语言打印空心菱形代码
  5. 本地计算机上的mysql服务启动后停止
  6. mysql 命令行 结束编辑_MySql命令行执行多行命令编辑时非常有用的命令
  7. 华三(H3C)在中国市场的份额已连续三年超过思科-进军海外市场
  8. 双核奔腾超频冲上5.8GHz,暴涨87%
  9. xshell与vim命令合集
  10. 单女最容易邂逅好男人的10个地方