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


目录
相关文章
|
2月前
|
SQL Java 关系型数据库
Spring事务传播机制:7种姿势教你玩转"事务接力赛"
事务传播机制是Spring框架中用于管理事务行为的重要概念,它决定了在方法调用时事务如何传递与执行。通过7种传播行为,开发者可以灵活控制事务边界,适应不同业务场景。例如:REQUIRED默认加入或新建事务,REQUIRES_NEW独立开启新事务,NESTED支持嵌套回滚等。合理使用传播机制不仅能保障数据一致性,还能提升系统性能与健壮性。掌握这“七种人格”,才能在复杂业务中游刃有余。
|
3月前
|
Java 关系型数据库 数据库
深度剖析【Spring】事务:万字详解,彻底掌握传播机制与事务原理
在Java开发中,Spring框架通过事务管理机制,帮我们轻松实现了这种“承诺”。它不仅封装了底层复杂的事务控制逻辑(比如手动开启、提交、回滚事务),还提供了灵活的配置方式,让开发者能专注于业务逻辑,而不用纠结于事务细节。
|
8月前
|
Java 关系型数据库 数据库
微服务——SpringBoot使用归纳——Spring Boot事务配置管理——常见问题总结
本文总结了Spring Boot中使用事务的常见问题,虽然通过`@Transactional`注解可以轻松实现事务管理,但在实际项目中仍有许多潜在坑点。文章详细分析了三个典型问题:1) 异常未被捕获导致事务未回滚,需明确指定`rollbackFor`属性;2) 异常被try-catch“吃掉”,应避免在事务方法中直接处理异常;3) 事务范围与锁范围不一致引发并发问题,建议调整锁策略以覆盖事务范围。这些问题看似简单,但一旦发生,排查难度较大,因此开发时需格外留意。最后,文章提供了课程源代码下载地址,供读者实践参考。
162 0
|
8月前
|
Java 关系型数据库 数据库
微服务——SpringBoot使用归纳——Spring Boot事务配置管理——Spring Boot 事务配置
本文介绍了 Spring Boot 中的事务配置与使用方法。首先需要导入 MySQL 依赖,Spring Boot 会自动注入 `DataSourceTransactionManager`,无需额外配置即可通过 `@Transactional` 注解实现事务管理。接着通过创建一个用户插入功能的示例,展示了如何在 Service 层手动抛出异常以测试事务回滚机制。测试结果表明,数据库中未新增记录,证明事务已成功回滚。此过程简单高效,适合日常开发需求。
1047 0
|
6月前
|
人工智能 Java 数据库连接
Spring事务失效场景
本文深入探讨了Spring框架中事务管理可能失效的几种常见场景及解决方案,包括事务方法访问级别不当、方法内部自调用、错误的异常处理、事务管理器或数据源配置错误、数据库不支持事务以及不合理的事务传播行为或隔离级别。通过合理配置和正确使用`@Transactional`注解,开发者可以有效避免这些问题,确保应用的数据一致性和完整性。
276 10
|
5月前
|
Java 关系型数据库 MySQL
【Spring】【事务】初学者直呼学会了的Spring事务入门
本文深入解析了Spring事务的核心概念与使用方法。Spring事务是一种数据库事务管理机制,通过确保操作的原子性、一致性、隔离性和持久性(ACID),维护数据完整性。文章详细讲解了声明式事务(@Transactional注解)和编程式事务(TransactionTemplate、PlatformTransactionManager)的区别与用法,并探讨了事务传播行为(如REQUIRED、REQUIRES_NEW等)及隔离级别(如READ_COMMITTED、REPEATABLE_READ)。
387 1
|
5月前
|
负载均衡 Java API
基于 Spring Cloud 的微服务架构分析
Spring Cloud 是一个基于 Spring Boot 的微服务框架,提供全套分布式系统解决方案。它整合了 Netflix、Zookeeper 等成熟技术,通过简化配置和开发流程,支持服务发现(Eureka)、负载均衡(Ribbon)、断路器(Hystrix)、API网关(Zuul)、配置管理(Config)等功能。此外,Spring Cloud 还兼容 Nacos、Consul、Etcd 等注册中心,满足不同场景需求。其核心组件如 Feign 和 Stream,进一步增强了服务调用与消息处理能力,为开发者提供了一站式微服务开发工具包。
539 0
|
7月前
|
SQL 前端开发 Java
深入分析 Spring Boot 项目开发中的常见问题与解决方案
本文深入分析了Spring Boot项目开发中的常见问题与解决方案,涵盖视图路径冲突(Circular View Path)、ECharts图表数据异常及SQL唯一约束冲突等典型场景。通过实际案例剖析问题成因,并提供具体解决方法,如优化视图解析器配置、改进数据查询逻辑以及合理使用外键约束。同时复习了Spring MVC视图解析原理与数据库完整性知识,强调细节处理和数据验证的重要性,为开发者提供实用参考。
288 0
|
12天前
|
XML 前端开发 Java
一文搞懂 Spring Boot 自动配置原理
Spring Boot 自动配置原理揭秘:通过 `@EnableAutoConfiguration` 加载 `META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports` 中的配置类,结合 `@Conditional` 按条件注入 Bean,实现“开箱即用”。核心在于约定大于配置,简化开发。
229 0

热门文章

最新文章

下一篇
开通oss服务