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);
}


目录
相关文章
|
4月前
|
设计模式 Java 开发者
如何快速上手【Spring AOP】?从动态代理到源码剖析(下篇)
Spring AOP的实现本质上依赖于代理模式这一经典设计模式。代理模式通过引入代理对象作为目标对象的中间层,实现了对目标对象访问的控制与增强,其核心价值在于解耦核心业务逻辑与横切关注点。在框架设计中,这种模式广泛用于实现功能扩展(如远程调用、延迟加载)、行为拦截(如权限校验、异常处理)等场景,为系统提供了更高的灵活性和可维护性。
|
8月前
|
前端开发 Java 物联网
智慧班牌源码,采用Java + Spring Boot后端框架,搭配Vue2前端技术,支持SaaS云部署
智慧班牌系统是一款基于信息化与物联网技术的校园管理工具,集成电子屏显示、人脸识别及数据交互功能,实现班级信息展示、智能考勤与家校互通。系统采用Java + Spring Boot后端框架,搭配Vue2前端技术,支持SaaS云部署与私有化定制。核心功能涵盖信息发布、考勤管理、教务处理及数据分析,助力校园文化建设与教学优化。其综合性和可扩展性有效打破数据孤岛,提升交互体验并降低管理成本,适用于日常教学、考试管理和应急场景,为智慧校园建设提供全面解决方案。
514 70
|
6月前
|
负载均衡 Java API
基于 Spring Cloud 的微服务架构分析
Spring Cloud 是一个基于 Spring Boot 的微服务框架,提供全套分布式系统解决方案。它整合了 Netflix、Zookeeper 等成熟技术,通过简化配置和开发流程,支持服务发现(Eureka)、负载均衡(Ribbon)、断路器(Hystrix)、API网关(Zuul)、配置管理(Config)等功能。此外,Spring Cloud 还兼容 Nacos、Consul、Etcd 等注册中心,满足不同场景需求。其核心组件如 Feign 和 Stream,进一步增强了服务调用与消息处理能力,为开发者提供了一站式微服务开发工具包。
634 0
|
8月前
|
SQL 前端开发 Java
深入分析 Spring Boot 项目开发中的常见问题与解决方案
本文深入分析了Spring Boot项目开发中的常见问题与解决方案,涵盖视图路径冲突(Circular View Path)、ECharts图表数据异常及SQL唯一约束冲突等典型场景。通过实际案例剖析问题成因,并提供具体解决方法,如优化视图解析器配置、改进数据查询逻辑以及合理使用外键约束。同时复习了Spring MVC视图解析原理与数据库完整性知识,强调细节处理和数据验证的重要性,为开发者提供实用参考。
347 0
|
9月前
|
存储 监控 数据可视化
SaaS云计算技术的智慧工地源码,基于Java+Spring Cloud框架开发
智慧工地源码基于微服务+Java+Spring Cloud +UniApp +MySql架构,利用传感器、监控摄像头、AI、大数据等技术,实现施工现场的实时监测、数据分析与智能决策。平台涵盖人员、车辆、视频监控、施工质量、设备、环境和能耗管理七大维度,提供可视化管理、智能化报警、移动智能办公及分布计算存储等功能,全面提升工地的安全性、效率和质量。
241 0
|
5月前
|
Java Spring 容器
SpringBoot自动配置的原理是什么?
Spring Boot自动配置核心在于@EnableAutoConfiguration注解,它通过@Import导入配置选择器,加载META-INF/spring.factories中定义的自动配置类。这些类根据@Conditional系列注解判断是否生效。但Spring Boot 3.0后已弃用spring.factories,改用新格式的.imports文件进行配置。
923 0
|
6月前
|
人工智能 Java 测试技术
Spring Boot 集成 JUnit 单元测试
本文介绍了在Spring Boot中使用JUnit 5进行单元测试的常用方法与技巧,包括添加依赖、编写测试类、使用@SpringBootTest参数、自动装配测试模块(如JSON、MVC、WebFlux、JDBC等),以及@MockBean和@SpyBean的应用。内容实用,适合Java开发者参考学习。
658 0
|
2月前
|
JavaScript Java Maven
【SpringBoot(二)】带你认识Yaml配置文件类型、SpringMVC的资源访问路径 和 静态资源配置的原理!
SpringBoot专栏第二章,从本章开始正式进入SpringBoot的WEB阶段开发,本章先带你认识yaml配置文件和资源的路径配置原理,以方便在后面的文章中打下基础
314 3
|
2月前
|
Java 测试技术 数据库连接
【SpringBoot(四)】还不懂文件上传?JUnit使用?本文带你了解SpringBoot的文件上传、异常处理、组件注入等知识!并且带你领悟JUnit单元测试的使用!
Spring专栏第四章,本文带你上手 SpringBoot 的文件上传、异常处理、组件注入等功能 并且为你演示Junit5的基础上手体验
861 2