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,后续会有更多实战、源码、架构干货分享!

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

vnjohn
+关注
目录
打赏
0
0
0
0
242
分享
相关文章
Spring中事务失效的场景
因为Spring事务是基于代理来实现的,所以某个加了@Transactional的⽅法只有是被代理对象调⽤时, 那么这个注解才会⽣效 , 如果使用的是被代理对象调用, 那么@Transactional会失效 同时如果某个⽅法是private的,那么@Transactional也会失效,因为底层cglib是基于⽗⼦类来实现 的,⼦类是不能重载⽗类的private⽅法的,所以⽆法很好的利⽤代理,也会导致@Transactianal失效 如果在业务中对异常进行了捕获处理 , 出现异常后Spring框架无法感知到异常, @Transactional也会失效
微服务——SpringBoot使用归纳——Spring Boot事务配置管理——常见问题总结
本文总结了Spring Boot中使用事务的常见问题,虽然通过`@Transactional`注解可以轻松实现事务管理,但在实际项目中仍有许多潜在坑点。文章详细分析了三个典型问题:1) 异常未被捕获导致事务未回滚,需明确指定`rollbackFor`属性;2) 异常被try-catch“吃掉”,应避免在事务方法中直接处理异常;3) 事务范围与锁范围不一致引发并发问题,建议调整锁策略以覆盖事务范围。这些问题看似简单,但一旦发生,排查难度较大,因此开发时需格外留意。最后,文章提供了课程源代码下载地址,供读者实践参考。
37 0
微服务——SpringBoot使用归纳——Spring Boot事务配置管理——Spring Boot 事务配置
本文介绍了 Spring Boot 中的事务配置与使用方法。首先需要导入 MySQL 依赖,Spring Boot 会自动注入 `DataSourceTransactionManager`,无需额外配置即可通过 `@Transactional` 注解实现事务管理。接着通过创建一个用户插入功能的示例,展示了如何在 Service 层手动抛出异常以测试事务回滚机制。测试结果表明,数据库中未新增记录,证明事务已成功回滚。此过程简单高效,适合日常开发需求。
73 0
微服务——SpringBoot使用归纳——Spring Boot事务配置管理——事务相关
本文介绍Spring Boot事务配置管理,阐述事务在企业应用开发中的重要性。事务确保数据操作可靠,任一异常均可回滚至初始状态,如转账、购票等场景需全流程执行成功才算完成。同时,事务管理在Spring Boot的service层广泛应用,但根据实际需求也可能存在无需事务的情况,例如独立数据插入操作。
25 0
Spring中的事务是如何实现的
1. Spring事务底层是基于数据库事务和AOP机制的 2. ⾸先对于使⽤了@Transactional注解的Bean,Spring会创建⼀个代理对象作为Bean 3. 当调⽤代理对象的⽅法时,会先判断该⽅法上是否加了@Transactional注解 4. 如果加了,那么则利⽤事务管理器创建⼀个数据库连接 5. 并且修改数据库连接的autocommit属性为false,禁⽌此连接的⾃动提交,这是实现Spring事务⾮ 常重要的⼀步 6. 然后执⾏当前⽅法,⽅法中会执⾏sql 7. 执⾏完当前⽅法后,如果没有出现异常就直接提交事务 8. 如果出现了异常,并且这个异常是需要回滚的就会回滚事务
微服务——SpringBoot使用归纳——Spring Boot集成MyBatis——基于 xml 的整合
本教程介绍了基于XML的MyBatis整合方式。首先在`application.yml`中配置XML路径,如`classpath:mapper/*.xml`,然后创建`UserMapper.xml`文件定义SQL映射,包括`resultMap`和查询语句。通过设置`namespace`关联Mapper接口,实现如`getUserByName`的方法。Controller层调用Service完成测试,访问`/getUserByName/{name}`即可返回用户信息。为简化Mapper扫描,推荐在Spring Boot启动类用`@MapperScan`注解指定包路径避免逐个添加`@Mapper`
44 0
微服务——SpringBoot使用归纳——Spring Boot集成Thymeleaf模板引擎——Thymeleaf 介绍
本课介绍Spring Boot集成Thymeleaf模板引擎。Thymeleaf是一款现代服务器端Java模板引擎,支持Web和独立环境,可实现自然模板开发,便于团队协作。与传统JSP不同,Thymeleaf模板可以直接在浏览器中打开,方便前端人员查看静态原型。通过在HTML标签中添加扩展属性(如`th:text`),Thymeleaf能够在服务运行时动态替换内容,展示数据库中的数据,同时兼容静态页面展示,为开发带来灵活性和便利性。
52 0
微服务——SpringBoot使用归纳——Spring Boot中的项目属性配置——少量配置信息的情形
本课主要讲解Spring Boot项目中的属性配置方法。在实际开发中,测试与生产环境的配置往往不同,因此不应将配置信息硬编码在代码中,而应使用配置文件管理,如`application.yml`。例如,在微服务架构下,可通过配置文件设置调用其他服务的地址(如订单服务端口8002),并利用`@Value`注解在代码中读取这些配置值。这种方式使项目更灵活,便于后续修改和维护。
23 0
微服务——SpringBoot使用归纳——Spring Boot使用slf4j进行日志记录—— application.yml 中对日志的配置
在 Spring Boot 项目中,`application.yml` 文件用于配置日志。通过 `logging.config` 指定日志配置文件(如 `logback.xml`),实现日志详细设置。`logging.level` 可定义包的日志输出级别,例如将 `com.itcodai.course03.dao` 包设为 `trace` 级别,便于开发时查看 SQL 操作。日志级别从高到低为 ERROR、WARN、INFO、DEBUG,生产环境建议调整为较高级别以减少日志量。本课程采用 yml 格式,因其层次清晰,但需注意格式要求。
90 0
|
29天前
|
基于SpringBoot的Redis开发实战教程
Redis在Spring Boot中的应用非常广泛,其高性能和灵活性使其成为构建高效分布式系统的理想选择。通过深入理解本文的内容,您可以更好地利用Redis的特性,为应用程序提供高效的缓存和消息处理能力。
150 79