Spring 事务执行流程及源码分析

1,912 阅读23分钟

了解整个事务的执行过程,那么就必须要得先了解事务的传播特性和隔离级别

传播特性

传播特性有以下七种,传播属性默认值为 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 回滚事务
  • 最终完成事务的整个过程,最终释放数据库连接

事务执行流程

在执行程序业务逻辑前,如果其被事务代理所修饰,会代理执行 DynamicAdvisedInterceptor#intercept 方法,获取到拦截器责任链 advisor 后,链式调用 proceed 方法;整个事务处理过程中,包含了以下 advisor

  • ExposeInovacationInterceptor :为了方便责任链的调用,作为前驱,联系者,协调其他 advisor 的运行
  • DefaultBeanFactoryPointcutAdvisor:其父类为 AbstractBeanFactoryPointcutAdvisor,内部拥有 advice,会在组装拦截器时调用 getAdvice() 方法获取到 TransactionInterceptor 拦截器
  • 最后组装完以后的拦截器链:ExposeInovacationInterceptor —> TransactionInterceptor

事务核心类

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):判断当前线程是否存在事务连接持有器,第一次进来的都是没有连接持有器的,所以第二次方法调用时才会进来;如果当前存在事务,就处理存在事务的逻辑,先判别内层事务的传播机制👇
      • NERVER(从不使用事务,则存在抛出异常),则抛出异常结束
      • NOT_SUPPORTED(不支持事务),挂起当前事务:将外层事务相关的连接持有器和属性封装为 SuspendedResourcesHolder 返回,并且创建的一个新的非事务的状态,同时将外层事务的挂起资源持有器作为参数一起进行实例化
      • REQURES_NEW(挂起已经存在的事务,开启一个新的事务)挂起当前事务后「1.清空线程本地的连接持有器;2.清空线程本地资源的所有资源信息;3.将之前的事务属性和连接器这些信息保存到 oldTransaction 变量里面;4.返回挂起的事务信息」startTransaction:开启一个新的事务状态,同时将外层事务的挂起资源作为参数一起实例化,newTransaction、newSynchronization 属性值都为 true,开辟一个新的连接、设置好事务同步管理器中的线程本地变量「事务是否激活状态、事务的隔离级别、是否为只读事务、事务名称」
      • NESTED(存在事务就使用,不存在就创建一个新的,并且设置一个保存点):通过当前的事务再次构建一个 DefaultTransactionStatus 对象,newTransaction、newSynchronization 属性值都改为 false,并为当前创建好的事务状态对象设置一个保存点.
      • 其他类型的传播机制:SUPPORTED、REQURED、MANDATORY,通过当前的事务再次构建一个 DefaultTransactionStatus 对象,newTransaction、newSynchronization 属性值都改为 false 后返回
    • 当前不存在事务,说明当前方法的事务是最外层的,先判断当前的超时时间是否设置正确「如果小于 -1 则为无效的超时时间就抛出异常」,有效后就走如下分支判别其事务的传播机制
      • MANDATORY(不存在事务则抛出异常),则抛出异常结束
      • REQUIRED、REQUIRES_NEW、NESTED 的话,开启创建事务信息的过程「1、挂起一个空的事务,因为当前事务是属于最外层的;2、如上的:startTransaction」
      • SUPPORTED、NERVER、NOT_SUPPORTED 类型传播机制的话,那么就创建一个空的事务
    • 此时,事务状态信息就创建完成了
  • 创建事务信息:prepareTransactionInfo,根据上一步的创建的状态信息以及「事务管理器、事务属性信息、全限定的方法名称」,标识事务信息为新事务状态信息以后,最终将当前的事务信息绑定到线程本地上
  • 最后是调用实际程序代码中的处理逻辑,处理后的结果要么就是成功提交事务、要么就是失败进行事务的回滚操作

事务正常提交过程

如果整个事务正常执行完成,事务就需要正常提交了,执行方法: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);
}

事务异常回滚流程

如果在执行过程中,发生了异常,执行方法:completeTransactionAfterThrowing~AbstractPlatformTransactionManager#rollback

unexpected 这个一般是 false,除非是设置 rollback-only=true 才是 true 或者是内层异常了给予了回滚标记,如外层 REQUIRED 内层也是 REQUIRED,表示是全局的回滚标记。首先会进行回滚前回调,然后判断是否设置了保存点,比如 NESTED 会设置,要先回滚到保存点。如果状态是新的事务,那就进行回滚,如果不是新的,就设置一个回滚标记,内部是设置连接持有器回滚标记。然后回滚完成回调,根据事务状态信息,完成后数据清除和线程的私有资源解绑,重置连接自动提交,隔离级别,是否只读,释放连接,恢复挂起事务等

如下代码是具体处理异常时回滚的源码

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

传播机制内/外层事务 Test

代码部分

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();
        }
    }
}
<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>
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,执行结果如下:
    • 外层方法中有事务,直接挂起,内层方法没有异常情况的话直接顺利执行
    • 如果内层方法有异常的话,那么内层方法中已经执行的数据库操作不会触发回滚,而外层方法的事务会进行回滚操作
    • 同样,如果外层方法中出现了异常操作,那么内部方法是不会回滚的,只有外层事务才会回滚
  • 内层事务:REQUIRED_NEW,执行结果如下:
    • 如果外层方法中存在事务,内层方法在运行的时候会挂起外层事务并开启一个新的事务
    • 如果程序正常执行,则内层方法优先事务提交,然后外层方法再提交
    • 如果内层方法中存在异常,内层事务会优先回滚,外层方法事务也会回滚
    • 如果外层方法中存在异常,那么内层事务正常提交,而外层方法会进行回滚操作
  • 内层事务:NESTED,执行结果如下:
    • 如果外层方法中有事务,那么直接创建一个保存点,后续操作中如果没有异常情况,那么会清除保存点信息,并且在外层事务中进行提交操作
    • 如果内层方法中存在异常情况,那么会回滚到保存点,外层方法事务会直接进行回滚
    • 如果外层方法中存在异常情况,那么会内层方法会正常执行,并且执行完毕之后释放保存点,并且外层方法事务会进行回滚

外层事务:MANDATORY

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

外层事务:SUPPORTS

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

外层事务:NEVER

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

外层事务:NOT_SUPPORTED

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

外层事务:REQUIRED_NEW

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

外层事务:NESTED

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

总结传播机制 Test

如果自己动手把每一种情况都演示了,其实挺好理解的,关键是大家舍不舍得花费时间一个一个去验证,在面试过程中,可能会经常问一下两个问题

REQUIRED 和 NESTED 回滚的区别

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

REQUIRED_NEW 和 REQUIRED 区别

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

更多技术文章可以查看:vnjohn 个人博客