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

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

rollback 流程

如果在执行过程中,发生了异常,执行方法:completeTransactionAfterThrowing->AbstractPlatformTransactionManager#rollback,以下代码是具体处理异常时回滚的源码:

private void processRollback(DefaultTransactionStatus status, boolean unexpected) {
  try {
    // 意外的回滚
    boolean unexpectedRollback = unexpected;
    try {
      // 回滚完成前回调
      triggerBeforeCompletion(status);
      // 有保存点回滚到保存点
      if (status.hasSavepoint()) {
        if (status.isDebug()) {
          logger.debug("Rolling back transaction to savepoint");
        }
        status.rollbackToHeldSavepoint();
      }
      // 当前状态是一个新事务
      else if (status.isNewTransaction()) {
        if (status.isDebug()) {
          logger.debug("Initiating transaction rollback");
        }
        // 进行回滚
        doRollback(status);
      }
      else {
        // 内层事务为 REQUIRED 传播机制时会走这里,设置全局的回滚标记
        if (status.hasTransaction()) {
          if (status.isLocalRollbackOnly() || isGlobalRollbackOnParticipationFailure()) {
            if (status.isDebug()) {
              logger.debug("Participating transaction failed - marking existing transaction as rollback-only");
            }
            //设置连接要回滚标记,也就是全局回滚
            doSetRollbackOnly(status);
          }
          else {
            if (status.isDebug()) {
              logger.debug("Participating transaction failed - letting transaction originator decide on rollback");
            }
          }
        }
        else {
          logger.debug("Should roll back transaction but cannot - no transaction available");
        }
        if (!isFailEarlyOnGlobalRollbackOnly()) {
          unexpectedRollback = false;
        }
      }
    }
    catch (RuntimeException | Error ex) {
      triggerAfterCompletion(status, TransactionSynchronization.STATUS_UNKNOWN);
      throw ex;
    }
    // 回滚完成后回调
    triggerAfterCompletion(status, TransactionSynchronization.STATUS_ROLLED_BACK);
    // 存在全局的回滚标记导致抛出如下异常
    if (unexpectedRollback) {
      throw new UnexpectedRollbackException(
        "Transaction rolled back because it has been marked as rollback-only");
    }
  }
  finally {
    // 根据事务状态信息,完成后数据清除,和线程的私有资源解绑,重置连接自动提交,隔离级别,是否只读,释放连接,恢复挂起事务等
    cleanupAfterCompletion(status);
  }
}

unexpected 参数这个一般是 false,除非是设置 rollback-only=true 才是 true 或者是内层异常了给予了回滚标记

  • 如外层事务传播特性是 REQUIRED、内层事务传播特性也是 REQUIRED,表示是全局的回滚标记
  • 首先会在回滚之前进行回调,然后判断是否设置了保存点,比如 NESTED 传播特性会设置,要先回滚到保存点,如果状态是新的事务,那就进行回滚,如果不是新的,就设置一个回滚标记,内部是设置连接持有器的回滚标记
  • 最后回滚完成回调,根据事务状态信息,完成后数据清除和线程的私有资源解绑,重置连接自动提交,隔离级别,是否只读,释放连接,恢复挂起事务等

事务执行结果测试

通过不同的传播特性机制来演示,外层事务的传播特性为标题所标注的,然后内层事务通过设置不同的传播特性机制进行事务测试

代码

Dao、Service 交互代码

public class BookDao {
    JdbcTemplate jdbcTemplate;
    public JdbcTemplate getJdbcTemplate() {
        return jdbcTemplate;
    }
    public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
        this.jdbcTemplate = jdbcTemplate;
    }
    /**
     * 减库存,减去某本书的库存
     *
     * @param id
     */
    public void updateStock(int id) {
        try {
            String sql = "update book_stock set stock=stock-1 where id=?";
            jdbcTemplate.update(sql, id);
            for (int i = 1; i >= 0; i--)
                System.out.println(10 / i);
        }catch (Exception e) {
        }
    }
}
public class BookService {
    BookDao bookDao;
    public BookDao getBookDao() {
        return bookDao;
    }
    public void setBookDao(BookDao bookDao) {
        this.bookDao = bookDao;
    }
    /**
     * 结账:传入哪个用户买了哪本书
     */
    public void checkout(String username, int id) {
        try {
            bookDao.updateStock(id);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

XML 配置文件

<context:property-placeholder location="classpath:dbconfig.properties"/>
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
  <property name="username" value="${jdbc.username}"/>
  <property name="password" value="${jdbc.password}"/>
  <property name="url" value="${jdbc.url}"/>
  <property name="driverClassName" value="${jdbc.driverClassName}"/>
</bean>
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate" >
  <constructor-arg name="dataSource" ref="dataSource"/>
</bean>
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
  <property name="dataSource" ref="dataSource"/>
</bean>
<bean id="bookService" class="com.vnjohn.tx.xml.service.BookService">
  <property name="bookDao" ref="bookDao"/>
</bean>
<bean id="bookDao" class="com.vnjohn.tx.xml.dao.BookDao">
  <property name="jdbcTemplate" ref="jdbcTemplate"/>
</bean>
<aop:config>
  <aop:pointcut id="txPoint" expression="execution(* com.vnjohn.tx.xml.*.*.*(..))"/>
  <aop:advisor advice-ref="myAdvice"  pointcut-ref="txPoint"/>
</aop:config>
<tx:advice id="myAdvice" transaction-manager="transactionManager">
  <tx:attributes>
    <tx:method name="checkout" propagation="REQUIRED"/>
    <tx:method name="updateStock" propagation="REQUIRED"/>
  </tx:attributes>
</tx:advice>

核心 Test 入口类

public class TxTest {
    public static void main(String[] args) throws SQLException {
        System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY,"d:\\code");
        ApplicationContext context = new ClassPathXmlApplicationContext("tx.xml");
        BookService bookService = context.getBean("bookService", BookService.class);
        bookService.checkout("vnjohn",1);
    }
}

REQUIRED

  • 内层事务:MANDATORY、REQUIRED、SUPPORTS,执行结果如下:
    如果程序正常执行,那么内层事务不会提交,在外部事务中统一进行事务提交 如果内层事务,或者外层事务中出现异常情况,那么会在外层事务的处理中统一进行异常回滚
  • 内层事务:NEVER,执行结果如下:
    外层方法不能出现事务,如果出现事务则直接报错
  • 内层事务:NOT_SUPPORTED,执行结果如下:
    1、外层方法中有事务,直接挂起,内层方法没有异常情况的话直接顺利执行
    2、若内层方法有异常的话,那么内层方法中已经执行的数据库操作不会触发回滚,而外层方法的事务会进行回滚操作
    3、同样,如果外层方法中出现了异常操作,那么内部方法是不会回滚的,只有外层事务才会回滚
  • 内层事务:REQUIRED_NEW,执行结果如下:
    1、若外层方法中存在事务,内层方法在运行的时候会挂起外层事务并开启一个新的事务
    2、若程序正常执行,则内层方法优先事务提交,然后外层方法再提交
    3、若内层方法中存在异常,内层事务会优先回滚,外层方法事务也会回滚
    4、若外层方法中存在异常,那么内层事务正常提交,而外层方法会进行回滚操作
  • 内层事务:NESTED,执行结果如下:
    1、若外层方法中有事务,那么直接创建一个保存点,后续操作中如果没有异常情况,那么会清除保存点信息,并且在外层事务中进行提交操作
    2、若内层方法中存在异常情况,那么会回滚到保存点,外层方法事务会直接进行回滚
    3、若外层方法中存在异常情况,那么会内层方法会正常执行,并且执行完毕之后释放保存点,并且外层方法事务会进行回滚

MANDATORY

内层事务不管是那种传播机制,其执行结果都是一样的,MANDATORY 不可以作为外层事务,在运行的时候必须需要一个事务,如果没有事务,会抛出异常

SUPPORTS

  • 内层事务:MANDATORY,执行结果如下:
    外层方法中如果不包含事务的话,那么内层方法在获取事务对象的时候直接报错,而外层方法中不包含事务,所以无需回滚
  • 内层事务:REQUIRED,执行结果如下:
    1、外层方法中不包含事务,所以内层方法会新建一个事务,如果程序正常执行,那么事务会正常提交
    2、若内层方法中出现异常,则内层方法事务正常回滚,而外层事务不做任何处理
    3、若外层方法中出现异常,则内层方法事务正常提交,外层方法抛出异常
  • 内层事务:SUPPORTS、NEVER、NOT_SUPPORTED,执行结果如下:
    1、内外层方法都不包含事务的话,会以无事务的方法开始运行,每个数据库操作直接执行即可
    2、如果出现异常情况,则后续的操作不会执行,但已经执行过的数据库操作不受任何影响
  • 内层事务:REQUIRED_NEW、NESTED,执行结果如下:
    1、外层方法中不包含事务,所以内层方法会新建一个事务,如果程序正常执行,那么事务会正常提交
    2、若内层方法中出现异常,则内层方法事务正常回滚,而外层事务不做任何处理
    3、若外层方法中出现异常,则内层方法事务正常提交,外层方法抛出异常

NEVER

  • 内层事务:MANDATORY,执行结果如下:
    1、外层方法中如果不包含事务的话,那么内层方法在获取事务对象的时候直接报错,而外层方法中不包含事务,所以无需回滚
  • 内层事务:REQUIRED,执行结果如下:
    1、外层方法中不包含事务,所以内层方法会新建一个事务,如果程序正常执行,那么事务会正常提交
    2、若内层方法中出现异常,则内层方法事务正常回滚,而外层事务不做任何处理
    3、若外层方法中出现异常,则内层方法事务正常提交,外层方法抛出异常
  • 内层事务:SUPPORTS、NEVER、NOT_SUPPORTED,执行结果如下:
    1、内外层方法都不包含事务的话,会以无事务的方法开始运行,每个数据库操作直接执行即可
    2、若出现异常情况,则后续的操作不会执行,但已经执行过的数据库操作不受任何影响
  • 内层事务:REQUIRED_NEW、NESTED,执行结果如下:
    1、外层方法中不包含事务,所以内层方法会新建一个事务,如果程序正常执行,那么事务会正常提交
    2、若内层方法中出现异常,则内层方法事务正常回滚,而外层事务不做任何处理
    3、若外层方法中出现异常,则内层方法事务正常提交,外层方法抛出异常

NOT_SUPPORTED

  • 内层事务:MANDATORY,执行结果如下:
    外层方法中如果不包含事务的话,那么内层方法在获取事务对象的时候直接报错,而外层方法中不包含事务,所以无需回滚
  • 内层事务:REQUIRED,执行结果如下:
    1、外层方法中不包含事务,所以内层方法会新建一个事务,如果程序正常执行,那么事务会正常提交
    2、若内层方法中出现异常,则内层方法事务正常回滚,而外层事务不做任何处理
    3、若外层方法中出现异常,则内层方法事务正常提交,外层方法抛出异常
  • 内层事务:SUPPORTS、NEVER、NOT_SUPPORTED,执行结果如下:
    1、内外层方法都不包含事务的话,会以无事务的方法开始运行,每个数据库操作直接执行即可
    2、若出现异常情况,则后续的操作不会执行,但已经执行过的数据库操作不受任何影响
  • 内层事务:REQUIRED_NEW、NESTED,执行结果如下:
    1、外层方法中不包含事务,所以内层方法会新建一个事务,如果程序正常执行,那么事务会正常提交
    2、若内层方法中出现异常,则内层方法事务正常回滚,而外层事务不做任何处理
    3、若外层方法中出现异常,则内层方法事务正常提交,外层方法抛出异常

REQUIRED_NEW

  • 内层事务:MANDATORY、REQUIRED、SUPPORTS,执行结果如下:
    1、若程序正常执行,那么内层事务不会提交,在外部事务中统一进行事务提交
    2、若内层事务,或者外层事务中出现异常情况,那么会在外层事务的处理中统一进行异常回滚
  • 内层事务:NEVER,执行结果如下:
    外层方法不能出现事务,如果出现事务则直接报错
  • 内层事务:NOT_SUPPORTED,执行结果如下:
    1、外层方法中有事务,直接挂起,内层方法没有异常情况的话直接顺利执行
    2、若内层方法有异常的话,那么内层方法中已经执行的数据库操作不会触发回滚,而外层方法的事务会进行回滚操作
    3、同样,如果外层方法中出现了异常操作,那么内部方法是不会回滚的,只有外层事务才会回滚
  • 内层事务:REQUIRED_NEW,执行结果如下:
    1、若外层方法中存在事务,内层方法在运行的时候会挂起外层事务并开启一个新的事务
    2、若程序正常执行,则内层方法优先事务提交,然后外层方法再提交
    3、若内层方法中存在异常,内层事务会优先回滚,外层方法事务也会回滚
    4、若外层方法中存在异常,那么内层事务正常正常提交,而外层方法会进行回滚操作
  • 内层事务:NESTED,执行结果如下:
    1、若外层方法中有事务,那么直接创建一个保存点
    2、若外层方法没有事务,那么就创建一个新的事务,后续操作中如果没有异常情况,那么会清除保存点信息,并且在外层事务中进行提交操作
    3、若内层方法中存在异常情况,那么会回滚到保存点,外层方法事务会直接进行回滚
    4、若外层方法中存在异常情况,那么内层方法不会正常执行,只是在执行完毕之后释放保存点「因为 NESTED 使用的是同一个事务」,然后统一在外层方法中会进行事务的回滚

NESTED

  • 内层事务:MANDATORY、REQUIRED、SUPPORTS,执行结果如下:
    1、若程序正常执行,那么内层事务不会提交,在外部事务中统一进行事务提交
    2、若内层事务或者外层事务中出现异常情况,那么会在外层事务的处理中统一进行异常回滚
  • 内层事务:NEVER,执行结果如下:
    1、外层方法不能出现事务,如果出现事务则直接报错
  • 内层事务:NOT_SUPPORTED,执行结果如下:
    1、外层方法中有事务,直接挂起,内层方法没有异常情况的话直接顺利执行
    2、若内层方法有异常的话,那么内层方法中已经执行的数据库操作不会触发回滚,而外层方法的事务会进行回滚操作
    3、同样,如果外层方法中出现了异常操作,那么内部方法是不会回滚的,只有外层事务才会回滚
  • 内层事务:REQUIRED_NEW,执行结果如下:
    1、若外层方法中存在事务,内层方法在运行的时候会挂起外层事务并开启一个新的事务及连接
    2、若程序正常执行,则内层方法优先事务提交,然后外层方法再提交
    3、若内层方法中存在异常,内层事务会优先回滚,外层方法事务也会回滚
    4、若外层方法中存在异常,那么内层事务正常提交,而外层方法会进行回滚操作
  • 内层事务:NESTED,执行结果如下:
    1、若外层方法中有事务,那么直接创建一个保存点,如果外层方法没有事务,那么就创建一个新的事务,后续操作中如果没有异常情况,那么会清除保存点信息,并且在外层事务中进行提交操作
    2、若内层方法中存在异常情况,那么会回滚到保存点,外层方法事务会直接进行回滚
    3、若外层方法中存在异常情况,那么内层方法会执行回滚,并且执行完毕之后释放保存点,并且外层方法事务也会进行回滚

FAQ

REQUIRED 和 NESTED 回滚的区别?

在回答两种方式区别的时候,最大的问题在于保存点的设置,你可能会认为内部设置 REQUIRED 和 NESTED 效果是一样的,其实在外层方法对内层方法的异常情况在进行捕获的时候区别很大,两者报的异常信息都不同,使用 REQUIRED 的时候,会报 Transaction rolled back because it has been marked as rollback-only 信息,因为内部异常了,设置了回滚标记,外部捕获之后,要进行事务的提交,此时发现有回滚标记,那么意味着要回滚,所以会报异常,而 NESTED 不会发生这种情况,因为在回滚的时候把回滚标记清除了,外部捕获异常后去提交,没发现回滚标记,就可以正常提交了

REQUIRED_NEW 和 NESTED 区别

这两种方式产生的效果是一样的,但是 REQUIRED_NEW 会有新的连接生成,而 NESTED 使用的是当前事务的连接,而且 NESTED 还可以回滚到保存点,REQUIRED_NEW 每次都是一个新的事务,没有办法控制其他事务的回滚,但 NESTED 其实是一个事务,外层事务可以控制内层事务的回滚,内层就算没有异常,外层出现异常,也可以全部回滚

总结

一般在面试过程中经常会问到这种事务相关的笔试题,让你现场进行判别事务是否正常提交还是回滚,所以这里面将所有涉及到的传播特性机制的事务特点都演示了一遍,主要还是要自己去动手断点观察!

如果觉得博文不错,关注我 vnjohn,后续会有更多实战、源码、架构干货分享!

大家的「关注❤️ + 点赞👍 + 收藏⭐」就是我创作的最大动力!谢谢大家的支持,我们下文见!

目录
相关文章
|
10天前
|
数据采集 监控 前端开发
二级公立医院绩效考核系统源码,B/S架构,前后端分别基于Spring Boot和Avue框架
医院绩效管理系统通过与HIS系统的无缝对接,实现数据网络化采集、评价结果透明化管理及奖金分配自动化生成。系统涵盖科室和个人绩效考核、医疗质量考核、数据采集、绩效工资核算、收支核算、工作量统计、单项奖惩等功能,提升绩效评估的全面性、准确性和公正性。技术栈采用B/S架构,前后端分别基于Spring Boot和Avue框架。
|
2天前
|
Java 开发者 Spring
Spring高手之路24——事务类型及传播行为实战指南
本篇文章深入探讨了Spring中的事务管理,特别是事务传播行为(如REQUIRES_NEW和NESTED)的应用与区别。通过详实的示例和优化的时序图,全面解析如何在实际项目中使用这些高级事务控制技巧,以提升开发者的Spring事务管理能力。
9 1
Spring高手之路24——事务类型及传播行为实战指南
|
26天前
|
Java Spring
Spring底层架构源码解析(三)
Spring底层架构源码解析(三)
|
26天前
|
XML Java 数据格式
Spring底层架构源码解析(二)
Spring底层架构源码解析(二)
|
26天前
|
XML Java 数据格式
手动开发-简单的Spring基于注解配置的程序--源码解析
手动开发-简单的Spring基于注解配置的程序--源码解析
42 0
|
2月前
|
SQL 监控 druid
springboot-druid数据源的配置方式及配置后台监控-自定义和导入stater(推荐-简单方便使用)两种方式配置druid数据源
这篇文章介绍了如何在Spring Boot项目中配置和监控Druid数据源,包括自定义配置和使用Spring Boot Starter两种方法。
|
26天前
|
人工智能 自然语言处理 前端开发
SpringBoot + 通义千问 + 自定义React组件:支持EventStream数据解析的技术实践
【10月更文挑战第7天】在现代Web开发中,集成多种技术栈以实现复杂的功能需求已成为常态。本文将详细介绍如何使用SpringBoot作为后端框架,结合阿里巴巴的通义千问(一个强大的自然语言处理服务),并通过自定义React组件来支持服务器发送事件(SSE, Server-Sent Events)的EventStream数据解析。这一组合不仅能够实现高效的实时通信,还能利用AI技术提升用户体验。
137 2
|
3月前
|
缓存 Java Maven
Java本地高性能缓存实践问题之SpringBoot中引入Caffeine作为缓存库的问题如何解决
Java本地高性能缓存实践问题之SpringBoot中引入Caffeine作为缓存库的问题如何解决
|
26天前
|
SQL JSON Java
mybatis使用三:springboot整合mybatis,使用PageHelper 进行分页操作,并整合swagger2。使用正规的开发模式:定义统一的数据返回格式和请求模块
这篇文章介绍了如何在Spring Boot项目中整合MyBatis和PageHelper进行分页操作,并且集成Swagger2来生成API文档,同时定义了统一的数据返回格式和请求模块。
44 1
mybatis使用三:springboot整合mybatis,使用PageHelper 进行分页操作,并整合swagger2。使用正规的开发模式:定义统一的数据返回格式和请求模块
|
4月前
|
Java 测试技术 数据库
Spring Boot中的项目属性配置
本节课主要讲解了 Spring Boot 中如何在业务代码中读取相关配置,包括单一配置和多个配置项,在微服务中,这种情况非常常见,往往会有很多其他微服务需要调用,所以封装一个配置类来接收这些配置是个很好的处理方式。除此之外,例如数据库相关的连接参数等等,也可以放到一个配置类中,其他遇到类似的场景,都可以这么处理。最后介绍了开发环境和生产环境配置的快速切换方式,省去了项目部署时,诸多配置信息的修改。