Spring 事务传播机制、隔离级别以及事务执行流程源码结合案例分析(上)

简介: Spring 事务传播机制、隔离级别以及事务执行流程源码结合案例分析

前言

了解整个事务的执行过程,那么就必须要要先了解事务的传播特性、隔离级别基础知识,以此为前提,才能知晓其下是如何贯通在一起使用的

传播特性简要

传播特性有以下七种,传播属性默认值为 REQUIRED:当前存在事务,就使用当前事务,否则创建一个新的事务

隔离级别简要

事务隔离级别指的是一个事务对数据的修改与另一个并行的事务的隔离程度,当多个事务同时访问相同数据时,如果没有采取必要的隔离机制,就可能发生以下问题:

  • 脏读:一个事务读到另一个事务未提交的更新数据,所谓脏读,就是指事务A读到了事务 B 还没有提交的数据,比如银行取钱,事务 A 开启事务,此时切换到事务 B,事务 B 开启事务–>取走100元,此时切换回事务 A,事务A读取的肯定是数据库里面的原始数据,因为事务 B 取走了 100 块钱,并没有提交,数据库里面的账务余额肯定还是原始余额,这就是脏读
  • 幻读:指当事务不是独立执行时发生的一种现象,例如第一个事务对一个表中的数据进行了修改,这种修改涉及到表中的全部数据行。 同时,第二个事务也修改这个表中的数据,这种修改是向表中插入一行新数据。那么,以后就会发生操作第一个事务的用户发现表中还有没有修改的数据行,就好象 发生了幻觉一样
  • 不可重复读:在一个事务里面的操作中发现了未被操作的数据,比方说在同一个事务中先后执行两条一模一样的 select 语句,期间在此次事务中没有执行过任何 DDL 语句,但先后得到的结果不一致,这就是不可重复读

Spring 中支持的隔离级别

  • DEFAULT:使用数据库本身使用的隔离级别->ORACLE(读已提交)、 MySQL(可重复读)
  • READ_UNCOMITTED:读未提交(脏读)最低的隔离级别,一切皆有可能
  • READ_COMMITED:读已提交,ORACLE默认隔离级别,有幻读以及不可重复读风险
  • REPEATABLE_READ:可重复读,解决不可重复读的隔离级别,但还是有幻读风险
  • SERLALIZABLE:串行化,最高的事务隔离级别,不管多少事务,挨个运行完一个事务的所有子事务之后才可以执行另外一个事务里面的所有子事务,这样就解决了脏读、不可重复读和幻读的问题了

再必须强调一遍,不是事务隔离级别设置得越高越好,事务隔离级别设置得越高,意味着势必要花手段去加锁用以保证事务的正确性,那么效率就要降低,因此实际开发中往往要在效率和并发正确性之间做一个取舍,一般情况下会设置为 READ_COMMITED,此时避免了脏读,并发性也还不错,之后再通过一些别的手段去解决不可重复读和幻读的问题就好了

如何设计一个事务系统

如果交给我们自己来设计一个事务系统时,一般分以下几个批次进行处理

  • 创建一个事务并且提前将事务的基本信息准备好,包括:事务属性、状态、管理器信息、数据库连接
  • 执行我们程序内部的业务逻辑
  • 判定在程序运行时是否发生了异常
  • 如果没发生异常,事务正常执行,并且将事务相关信息给清除,避免资源的消耗,commit 提交事务
  • 如果发生了异常,事务异常执行,清除事务的相关信息,并且恢复到事务前的一个状态,rollback 回滚事务
  • 最终完成事务的整个过程,最终释放数据库连接

事务执行流程 source coding

在执行程序业务逻辑前,如果其被事务代理所修饰,会代理执行 DynamicAdvisedInterceptor#intercept 方法,获取到拦截器责任链 advisor 后,链式调用 proceed 方法;整个事务处理过程中,包含了以下 advisor

  • ExposeInovacationInterceptor :为了方便责任链的调用,作为前驱,联系者,协调其他 advisor 的运行
  • DefaultBeanFactoryPointcutAdvisor:其父类为 AbstractBeanFactoryPointcutAdvisor,内部拥有 advice,会在组装拦截器时调用 getAdvice 方法获取到 TransactionInterceptor 拦截器
  • 最后组装完以后的拦截器链:ExposeInovacationInterceptor —> TransactionInterceptor

核心类 TransactionInfo

protected static final class TransactionInfo {
  @Nullable
  private final PlatformTransactionManager transactionManager; // 事务管理器
  @Nullable
  private final TransactionAttribute transactionAttribute; // 事务属性:传播机制、隔离级别、超时时间、是否只读
  private final String joinpointIdentification;
  @Nullable
  private TransactionStatus transactionStatus; // 事务状态:是否为新事务、是否是需要新同步、挂起的连接资源
  @Nullable
  private TransactionInfo oldTransactionInfo; // 同一个线程内旧的事务信息
  public TransactionInfo(@Nullable PlatformTransactionManager transactionManager,
                         @Nullable TransactionAttribute transactionAttribute, String joinpointIdentification) {
    this.transactionManager = transactionManager;
    this.transactionAttribute = transactionAttribute;
    this.joinpointIdentification = joinpointIdentification;
  }
  public PlatformTransactionManager getTransactionManager() {
    Assert.state(this.transactionManager != null, "No PlatformTransactionManager set");
    return this.transactionManager;
  }
  @Nullable
  public TransactionAttribute getTransactionAttribute() {
    return this.transactionAttribute;
  }
  // 获取方法的全限定名称
  public String getJoinpointIdentification() {
    return this.joinpointIdentification;
  }
  public void newTransactionStatus(@Nullable TransactionStatus status) {
    this.transactionStatus = status;
  }
  @Nullable
  public TransactionStatus getTransactionStatus() {
    return this.transactionStatus;
  }
  // 是否存在事务
  public boolean hasTransaction() {
    return (this.transactionStatus != null);
  }
  private void bindToThread() {
    // 暴露当前的事务状态,将存在的事务进行挂起,等待当前事务完成后再将已存在的事务进行恢复
    this.oldTransactionInfo = transactionInfoHolder.get();
    transactionInfoHolder.set(this);
  }
  private void restoreThreadLocalStatus() {
    // 将老的事务状态进行重新设定
    transactionInfoHolder.set(this.oldTransactionInfo);
  }
  @Override
  public String toString() {
    return (this.transactionAttribute != null ? this.transactionAttribute.toString() : "No transaction");
  }
}

调用事务方法的入口会到达 TransactionInterceptor#invoke#invokeWithinTransaction(以事务的方式调用目标方法,在这埋了一个钩子函数,用来回调目标方法的)事务的整个处理逻辑如下:

  • prepare 准备工作:作好事务需要的前期准备工作,获取当前的事务属性源信息以及事务管理器转换为 PlatformTransactionManager 类型「其提供三种能力:1、获取事务状态对象;2、提交事务;3、回滚事务」最后获取该方法的全限定方法名
  • 创建事务信息:createTransactionIfNecessary,先根据当前的事务属性创建委托类「DelegatingTransactionAttribute」
  • 根据事务属性委托类创建事务状态信息,分为以下几种情况
  • doGetTransaction:获取事务对象信息,默认第一次进来的是没有值的;它主要是从 TransactionSynchronizationManager 事务同步管理器获取到连接持有器,设置 newConnectionHolder=false 并返回数据源事务对象
  • isExistingTransaction(transaction):判断当前线程是否存在事务连接持有器,第一次进来的都是没有连接持有器的,所以第二次方法调用时才会进来;如果当前存在事务,就处理存在事务的逻辑,先判别内层事务的传播机制

1、NERVER(从不使用事务,则存在抛出异常),则抛出异常结束

2、NOT_SUPPORTED(不支持事务) 挂起当前事务:将外层事务相关的连接持有器和属性封装为 SuspendedResourcesHolder 返回,并且创建的一个新的非事务的状态,同时将外层事务的挂起资源持有器作为参数一起进行实例化

3、REQURES_NEW(挂起已经存在的事务,开启一个新的事务):挂起当前事务后「1.清空线程本地的连接持有器;2.清空线程本地资源的所有资源信息;3.将之前的事务属性和连接器这些信息保存到 oldTransaction 变量里面;4.返回挂起的事务信息」,startTransaction:开启一个新的事务状态,同时将外层事务的挂起资源作为参数一起实例化,newTransaction、newSynchronization 属性值都为 true,开辟一个新的连接、设置好事务同步管理器中的线程本地变量「事务是否激活状态、事务的隔离级别、是否为只读事务、事务名称」

4、NESTED(存在事务就使用,不存在就创建一个新的,并且设置一个保存点):通过当前的事务再次构建一个 DefaultTransactionStatus 对象,newTransaction、newSynchronization 属性值都改为 false,并为当前创建好的事务状态对象设置一个保存点.

5、其他类型的传播机制:SUPPORTED、REQURED、MANDATORY,通过当前的事务再次构建一个 DefaultTransactionStatus 对象,newTransaction、newSynchronization 属性值都改为 false 后返回

commit 流程

如果整个事务正常执行完成,事务就需要正常提交了,执行方法:commitTransactionAfterReturning->AbstractPlatformTransactionManage#commit「处理提交,先处理保存点,然后处理新事务,如果不是新事务不会真正提交,要等外层是新事务的才提交,最后根据条件执行数据清除、线程的私有资源解绑,重置连接自动提交、隔离级别、是否只读、释放连接、恢复挂起事务等」,以下是处理提交的源码:

private void processCommit(DefaultTransactionStatus status) throws TransactionException {
  try {
    boolean beforeCompletionInvoked = false;
    try {
      boolean unexpectedRollback = false;
      // 预留
      prepareForCommit(status);
      // 添加 TransactionSynchronization 中的对应方法的调用
      triggerBeforeCommit(status);
      // 提交完成前回调
      triggerBeforeCompletion(status);
      beforeCompletionInvoked = true;
      // 有保存点
      if (status.hasSavepoint()) {
        if (status.isDebug()) {
          logger.debug("Releasing transaction savepoint");
        }
        // 是否有全局回滚标记
        unexpectedRollback = status.isGlobalRollbackOnly();
        // 如果存在保存点则清除保存点信息
        status.releaseHeldSavepoint();
      }
      // 当前状态是新事务
      else if (status.isNewTransaction()) {
        if (status.isDebug()) {
          logger.debug("Initiating transaction commit");
        }
        unexpectedRollback = status.isGlobalRollbackOnly();
        // 如果是独立的事务则直接提交
        doCommit(status);
      }
      else if (isFailEarlyOnGlobalRollbackOnly()) {
        unexpectedRollback = status.isGlobalRollbackOnly();
      }
      // 有全局回滚标记就报异常
      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) {
      if (!beforeCompletionInvoked) {
        triggerBeforeCompletion(status);
      }
      // 提交过程中出现异常则回滚
      doRollbackOnCommitException(status, ex);
      throw ex;
    }
    try {
      // 提交后回调
      triggerAfterCommit(status);
    }
    finally {
      // 提交后清除线程私有同步状态
      triggerAfterCompletion(status, TransactionSynchronization.STATUS_COMMITTED);
    }
  }
  finally {
    //根据条件,完成后数据清除,和线程的私有资源解绑,重置连接自动提交,隔离级别,是否只读,释放连接,恢复挂起事务等
    cleanupAfterCompletion(status);
  }
}

无论是正常提交或者发生了异常,一些基本的资源应该被释放,清除当前的事务信息,恢复线程本地老的事务信息

private void restoreThreadLocalStatus() {
  transactionInfoHolder.set(this.oldTransactionInfo);
}


目录
相关文章
|
1月前
|
安全 Java 数据库
一天十道Java面试题----第四天(线程池复用的原理------>spring事务的实现方式原理以及隔离级别)
这篇文章是关于Java面试题的笔记,涵盖了线程池复用原理、Spring框架基础、AOP和IOC概念、Bean生命周期和作用域、单例Bean的线程安全性、Spring中使用的设计模式、以及Spring事务的实现方式和隔离级别等知识点。
|
5天前
|
缓存 Java 开发工具
Spring是如何解决循环依赖的?从底层源码入手,详细解读Spring框架的三级缓存
三级缓存是Spring框架里,一个经典的技术点,它很好地解决了循环依赖的问题,也是很多面试中会被问到的问题,本文从源码入手,详细剖析Spring三级缓存的来龙去脉。
Spring是如何解决循环依赖的?从底层源码入手,详细解读Spring框架的三级缓存
|
5天前
|
XML 缓存 Java
手写Spring源码(简化版)
Spring包下的类、手写@ComponentScan注解、@Component注解、@Autowired注解、@Scope注解、手写BeanDefinition、BeanNameAware、InitializingBean、BeanPostProcessor 、手写AnnotationConfigApplicationContext
手写Spring源码(简化版)
|
5天前
|
缓存 安全 Java
Spring框架中Bean是如何加载的?从底层源码入手,详细解读Bean的创建流程
从底层源码入手,通过代码示例,追踪AnnotationConfigApplicationContext加载配置类、启动Spring容器的整个流程,并对IOC、BeanDefinition、PostProcesser等相关概念进行解释
Spring框架中Bean是如何加载的?从底层源码入手,详细解读Bean的创建流程
|
22天前
|
人工智能 前端开发 Java
【实操】Spring Cloud Alibaba AI,阿里AI这不得玩一下(含前后端源码)
本文介绍了如何使用 **Spring Cloud Alibaba AI** 构建基于 Spring Boot 和 uni-app 的聊天机器人应用。主要内容包括:Spring Cloud Alibaba AI 的概念与功能,使用前的准备工作(如 JDK 17+、Spring Boot 3.0+ 及通义 API-KEY),详细实操步骤(涵盖前后端开发工具、组件选择、功能分析及关键代码示例)。最终展示了如何成功实现具备基本聊天功能的 AI 应用,帮助读者快速搭建智能聊天系统并探索更多高级功能。
167 2
【实操】Spring Cloud Alibaba AI,阿里AI这不得玩一下(含前后端源码)
|
1月前
|
Java 程序员 数据库连接
女朋友不懂Spring事务原理,今天给她讲清楚了!
该文章讲述了如何解释Spring事务管理的基本原理,特别是针对女朋友在面试中遇到的问题。文章首先通过一个简单的例子引入了传统事务处理的方式,然后详细讨论了Spring事务管理的实现机制。
女朋友不懂Spring事务原理,今天给她讲清楚了!
|
1月前
|
XML Java 数据库
Spring5入门到实战------15、事务操作---概念--场景---声明式事务管理---事务参数--注解方式---xml方式
这篇文章是Spring5框架的实战教程,详细介绍了事务的概念、ACID特性、事务操作的场景,并通过实际的银行转账示例,演示了Spring框架中声明式事务管理的实现,包括使用注解和XML配置两种方式,以及如何配置事务参数来控制事务的行为。
Spring5入门到实战------15、事务操作---概念--场景---声明式事务管理---事务参数--注解方式---xml方式
|
1月前
|
前端开发 Java 数据库连接
一天十道Java面试题----第五天(spring的事务传播机制------>mybatis的优缺点)
这篇文章总结了Java面试中的十个问题,包括Spring事务传播机制、Spring事务失效条件、Bean自动装配方式、Spring、Spring MVC和Spring Boot的区别、Spring MVC的工作流程和主要组件、Spring Boot的自动配置原理和Starter概念、嵌入式服务器的使用原因,以及MyBatis的优缺点。
|
1月前
|
Java 关系型数据库 MySQL
Spring Boot事务配置管理
主要总结了 Spring Boot 中如何使用事务,只要使用 @Transactional 注解即可使用,非常简单方便。除此之外,重点总结了三个在实际项目中可能遇到的坑点,这非常有意义,因为事务这东西不出问题还好,出了问题比较难以排查,所以总结的这三点注意事项,希望能帮助到开发中的朋友。
|
28天前
|
设计模式 Java 程序员
学习 Spring 源码的意义是什么呢?
研究Spring源码能深化框架理解,提升代码分析与设计能力,助您掌握设计模式及最佳实践,增强解决问题的效率,促进职业生涯发展,并激发技术热情。选择稳定版本,从核心模块开始,结合实际项目并参与社区,让学习之旅既充实又具乐趣。