浅析Spring的事务实现原理

目录
  • SQL事务实现简介
    • 编程式事务
    • 声明式事务
    • 注释事务属性源
    • 事务拦截器
    • Bean工厂事务属性源指导
  • 事务多样性支持
  • 总结

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();//执行第一个sql
            preparedStatement = 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
      @Nullable
      public Object proceedWithInvocation() throws Throwable {
         return invocation.proceed();
      }
      @Override
      public Object getTarget() {
         return invocation.getThis();
      }
      @Override
      public 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的事务实现原理的详细内容,更多关于Spring事务的资料请关注我们其它相关文章!

(0)

相关推荐

  • Spring事务原理解析

    目录 前言 问题描述 代码复现 排查 1. 锁失效 2. 事务隔离级别 3. 修改Spring事务传播配置 解决方案 前言 最近在编写公司APP产品的商品砍价功能,其中有一个接口涉及并发访问.自测时通过ApiFox接口管理工具进行压测,落地数据时出现了"锁失效"的情景.十分感谢后端小伙伴的帮助排查,解决了这个问题. 问题描述 并发接口中,先对主表数据进行读取,进行业务判断后,新增.修改它表的数据.在理应串行执行的情况下发生了多个请求线程读取到了相同的主表数据,导致数据处理异常.也正是前

  • Spring底层事务原理解析

    目录 一.@EnableTransactionManagement工作原理 二.Spring事务基本执行原理 四.Spring事务传播机制 五.Spring事务传播机制分类 六.Spring事务强制回滚 七.TransactionSynchronization 一.@EnableTransactionManagement工作原理 开启Spring事务本质上就是增加了一个Advisor,但我们使用 @EnableTransactionManagement注解来开启Spring事务是,该注解代理的功

  • Spring事务管理详细讲解

    目录 事务回顾 spring事务操作 基于注解声明事务 @Transactional注解使用 事务传播机制 事务隔离级别 @Transactional其他属性 基于XML 声明式事务 完全注解开发 说明:基于atguigu学习笔记. 事务回顾 事务是逻辑上的一组数据库操作,要么都执行,要么都不执行. 假如,张三给李四转账100元,转账行为欧两个关键操作:将张三的余额减200元,将李四的余额增加200元.如果两个操作之间突然出现错误,例如银行系统崩溃导致张三余额减少,而李四的余额没有增加,这样的系

  • Spring事务管理中关于数据库连接池详解

    目录 Spring事务管理 环境搭建 标准配置 声明式事务 总结 SqlSessionFactory XML中构建SqlSessionFactory 获得SqlSession的实例 代码实现 作用域(Scope)和生命周期 SqlSessionFactoryBuilder(构造器) SqlSessionFactory(工厂) SqlSession(会话) Spring事务管理 事务(Transaction),一般是指要做的或所做的事情.在计算机术语中是指访问并可能更新数据库中各种数据项的一个程序

  • Spring事务管理零基础入门

    目录 一.简介 二.特性(一原持久隔离) 2.1 原子性 2.2 一致性(类似能量守恒) 2.3 隔离性 2.4 持久性 三.隔离级别 3.1 事务级别(从低到高) 3.2 常用数据库默认级别: 3.3 事务中可能出现的问题: 四.传播特性 4.1 死活都不要事务 4.2 可有可无的 4.3 必须要有事务 五.应用 5.1 数据表 5.2 实体类 5.3 Service 一.简介 概念:事务是数据库操作的最小工作单元,是作为单个逻辑工作单元执行的一系列操作,这些操作一起提交,要么都执行,要么都不

  • 浅析Spring的事务实现原理

    目录 SQL事务实现简介 编程式事务 声明式事务 注释事务属性源 事务拦截器 Bean工厂事务属性源指导 事务多样性支持 总结 SQL事务实现简介 首先我们来了解下,最简单的事务是怎么实现的呢?以JDBC为例,当一个数据库Connection对象创建后,其会默认自动提交事务:每次执行SQL语句时,如果成功,就会向数据库自动提交,不能回滚. 通过调用setAutoCommit(false)方法可以取消自动提交事务.等到所有的SQL语句都执行成功后,调用commit()方法提交事务.如果其中某个操作

  • 深入浅析Spring 的aop实现原理

    什么是AOP AOP(Aspect-OrientedProgramming,面向方面编程),可以说是OOP(Object-Oriented Programing,面向对象编程)的补充和完善.OOP引入封装.继承和多态性等概念来建立一种对象层次结构,用以模拟公共行为的一个集合.当我们需要为分散的对象引入公共行为的时候,OOP则显得无能为力.也就是说,OOP允许你定义从上到下的关系,但并不适合定义从左到右的关系.例如日志功能.日志代码往往水平地散布在所有对象层次中,而与它所散布到的对象的核心功能毫无

  • Spring中事务用法示例及实现原理详解

    前言 Spring并不直接管理事务,而是提供了多种事务管理器,他们将事务管理的职责委托给Hibernate或者JTA等持久化机制所提供的相关平台框架的事务来实现. 关于事务,简单来说,就是为了保证数据完整性而存在的一种工具,其主要有四大特性:原子性,一致性,隔离性和持久性.对于Spring事务,其最终还是在数据库层面实现的,而Spring只是以一种比较优雅的方式对其进行封装支持.本文首先会通过一个简单的示例来讲解Spring事务是如何使用的,然后会讲解Spring是如何解析xml中的标签,并对事

  • Spring事务管理原理及方法详解

    这篇文章主要介绍了Spring事务管理原理及方法详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 事务,在日常开发或者面试中都必定会涉及到.开发工作中,结合数据库开发理解就是:一组dml要么全部成功执行提交,要么因为某一个操作异常,撤销之前所做的成功的操作,整体执行失败.再简单点的一句话:生死与共. 由此,可以看出,事务的必要性:在开发工作中,保证操作数据的安全性.事务的控制也就是保证数据的访问安全性. 一.事务的四大特性 A:原子性(ato

  • Spring事务annotation原理详解

    这篇文章主要介绍了Spring事务annotation原理详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 在使用Spring的时候,配置文件中我们经常看到 annotation-driven 这样的注解,其含义就是支持注解,一般根据前缀 tx.mvc 等也能很直白的理解出来分别的作用. <tx:annotation-driven/> 就是支持事务注解的(@Transactional) . <mvc:annotation-driven

  • Java Spring AOP源码解析之事务实现原理

    目录 不用Spring管理事务? 编程式事务管理 使用PlatformTransactionManager 使用TransactionTemplate 声明式事务管理 使用@Transactional注解 源码解析 参考博客 总结 不用Spring管理事务? 让我们先来看一下不用spring管理事务时,各种框架是如何管理事务的 使用JDBC来管理事务 使用Hibernate来管理事务 业务逻辑和事务代码是耦合到一块的,并且和框架的具体api绑定了.当我们换一种框架来实现时,里面对事务控制的代码就

  • 详解Java TCC分布式事务实现原理

    概述 之前网上看到很多写分布式事务的文章,不过大多都是将分布式事务各种技术方案简单介绍一下.很多朋友看了还是不知道分布式事务到底怎么回事,在项目里到底如何使用. 所以这篇文章,就用大白话+手工绘图,并结合一个电商系统的案例实践,来给大家讲清楚到底什么是 TCC 分布式事务. 业务场景介绍 咱们先来看看业务场景,假设你现在有一个电商系统,里面有一个支付订单的场景. 那对一个订单支付之后,我们需要做下面的步骤: 更改订单的状态为"已支付" 扣减商品库存 给会员增加积分 创建销售出库单通知仓

  • Spring TransactionalEventListener事务未提交读取不到数据的解决

    目录 一.背景 二.问题分析 2.1.mysql隔离级别 2.2.问题原因分析 三.解决问题方案 3.1.方式一 3.2.方式二 四.使用案例 一.背景 业务处理过程,发现了以下问题,代码一是原代码能正常执行,代码二是经过迭代一次非正常执行代码 代码一:以下代码开启线程后,代码正常执行 ThreadPoolExecutor executor = new ThreadPoolExecutor(5, 10, 200, TimeUnit.MILLISECONDS, new ArrayBlockingQ

  • Spring数据库事务的实现机制讲解

    目录 事务控制的核心--Connection 用AOP技术保持当前的Connection Service层和Dao层共享Connection 事务为什么要切在Service层的理由 spring事务与数据库事务的区别 事务控制的核心--Connection 在开始之前,先让我们回忆一下数据库较原始的JDBC是怎么管理事务的: //仅做演示,代码不完整,不完全规范 try { con.setAutoCommit(false); statement1 = con.prepareStatement(s

随机推荐