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月前
|
SQL Java 关系型数据库
Spring事务传播机制:7种姿势教你玩转"事务接力赛"
事务传播机制是Spring框架中用于管理事务行为的重要概念,它决定了在方法调用时事务如何传递与执行。通过7种传播行为,开发者可以灵活控制事务边界,适应不同业务场景。例如:REQUIRED默认加入或新建事务,REQUIRES_NEW独立开启新事务,NESTED支持嵌套回滚等。合理使用传播机制不仅能保障数据一致性,还能提升系统性能与健壮性。掌握这“七种人格”,才能在复杂业务中游刃有余。
|
11月前
|
Java 关系型数据库 数据库
深度剖析【Spring】事务:万字详解,彻底掌握传播机制与事务原理
在Java开发中,Spring框架通过事务管理机制,帮我们轻松实现了这种“承诺”。它不仅封装了底层复杂的事务控制逻辑(比如手动开启、提交、回滚事务),还提供了灵活的配置方式,让开发者能专注于业务逻辑,而不用纠结于事务细节。
1221 1
|
11月前
|
设计模式 Java 开发者
如何快速上手【Spring AOP】?从动态代理到源码剖析(下篇)
Spring AOP的实现本质上依赖于代理模式这一经典设计模式。代理模式通过引入代理对象作为目标对象的中间层,实现了对目标对象访问的控制与增强,其核心价值在于解耦核心业务逻辑与横切关注点。在框架设计中,这种模式广泛用于实现功能扩展(如远程调用、延迟加载)、行为拦截(如权限校验、异常处理)等场景,为系统提供了更高的灵活性和可维护性。
1422 0
|
人工智能 Java 数据库连接
Spring事务失效场景
本文深入探讨了Spring框架中事务管理可能失效的几种常见场景及解决方案,包括事务方法访问级别不当、方法内部自调用、错误的异常处理、事务管理器或数据源配置错误、数据库不支持事务以及不合理的事务传播行为或隔离级别。通过合理配置和正确使用`@Transactional`注解,开发者可以有效避免这些问题,确保应用的数据一致性和完整性。
1122 10
|
Java 关系型数据库 MySQL
【Spring】【事务】初学者直呼学会了的Spring事务入门
本文深入解析了Spring事务的核心概念与使用方法。Spring事务是一种数据库事务管理机制,通过确保操作的原子性、一致性、隔离性和持久性(ACID),维护数据完整性。文章详细讲解了声明式事务(@Transactional注解)和编程式事务(TransactionTemplate、PlatformTransactionManager)的区别与用法,并探讨了事务传播行为(如REQUIRED、REQUIRES_NEW等)及隔离级别(如READ_COMMITTED、REPEATABLE_READ)。
876 1
|
Java 关系型数据库 数据库连接
Spring源码解析--深入Spring事务原理
本文将带领大家领略Spring事务的风采,Spring事务是我们在日常开发中经常会遇到的,也是各种大小面试中的高频题,希望通过本文,能让大家对Spring事务有个深入的了解,无论开发还是面试,都不会让Spring事务成为拦路虎。
570 1
|
Java Spring 容器
Spring IOC、AOP与事务管理底层原理及源码解析
【10月更文挑战第1天】Spring框架以其强大的控制反转(IOC)和面向切面编程(AOP)功能,成为Java企业级开发中的首选框架。本文将深入探讨Spring IOC和AOP的底层原理,并通过源码解析来揭示其实现机制。同时,我们还将探讨Spring事务管理的核心原理,并给出相应的源码示例。
609 9
|
Java Spring
Spring底层架构源码解析(三)
Spring底层架构源码解析(三)
720 5
|
XML Java 数据格式
Spring底层架构源码解析(二)
Spring底层架构源码解析(二)
350 4