这一次搞懂Spring事务是如何传播的

前言

上一篇分析了事务注解的解析过程,本质上是将事务封装为切面加入到AOP的执行链中,因此会调用到MethodInceptor的实现类的invoke方法,而事务切面的Interceptor就是TransactionInterceptor,所以本篇直接从该类开始。

正文

事务切面的调用过程

 public Object invoke(MethodInvocation invocation) throws Throwable {
 // Work out the target class: may be {@code null}.
 // The TransactionAttributeSource should be passed the target class
 // as well as the method, which may be from an interface.
 Class<?> targetClass = (invocation.getThis() != null ? AopUtils.getTargetClass(invocation.getThis()) : null);

 // Adapt to TransactionAspectSupport's invokeWithinTransaction...
 return invokeWithinTransaction(invocation.getMethod(), targetClass, invocation::proceed);
 }

这个方法本身没做什么事,主要是调用了父类的invokeWithinTransaction方法,注意最后一个参数,传入的是一个lambda表达式,而这个表达式中的调用的方法应该不陌生,在分析AOP调用链时,就是通过这个方法传递到下一个切面或是调用被代理实例的方法,忘记了的可以回去看看。

 protected Object invokeWithinTransaction(Method method, @Nullable Class<?> targetClass,
 final InvocationCallback invocation) throws Throwable {

 // If the transaction attribute is null, the method is non-transactional.
 //获取事务属性类 AnnotationTransactionAttributeSource
 TransactionAttributeSource tas = getTransactionAttributeSource();

 //获取方法上面有@Transactional注解的属性
 final TransactionAttribute txAttr = (tas != null ? tas.getTransactionAttribute(method, targetClass) : null);

 //获取事务管理器
 final PlatformTransactionManager tm = determineTransactionManager(txAttr);
 final String joinpointIdentification = methodIdentification(method, targetClass, txAttr);

 if (txAttr == null || !(tm instanceof CallbackPreferringPlatformTransactionManager)) {
 TransactionInfo txInfo = createTransactionIfNecessary(tm, txAttr, joinpointIdentification);
 Object retVal = null;
 try {
 // 调用proceed方法
 retVal = invocation.proceedWithInvocation();
 }
 catch (Throwable ex) {
 // target invocation exception
 //事务回滚
 completeTransactionAfterThrowing(txInfo, ex);
 throw ex;
 }
 finally {
 cleanupTransactionInfo(txInfo);
 }
 //事务提交
 commitTransactionAfterReturning(txInfo);
 return retVal;
 }

 // 省略了else
 }

这个方法逻辑很清晰,一目了然,if里面就是对声明式事务的处理,先调用createTransactionIfNecessary方法开启事务,然后通过invocation.proceedWithInvocation调用下一个切面,如果没有其它切面了,就是调用被代理类的方法,出现异常就回滚,否则提交事务,这就是Spring事务切面的执行过程。但是,我们主要要搞懂的就是在这些方法中是如何管理事务以及事务在多个方法之间是如何传播的。

事务的传播性概念

传播性是Spring自己搞出来的,数据库是没有的,因为涉及到方法间的调用,那么必然就需要考虑事务在这些方法之间如何流转,所以Spring提供了7个传播属性供选择,可以将其看成两大类,即是否支持当前事务:

支持当前事务(在同一个事务中):

PROPAGATION_REQUIRED:支持当前事务,如果不存在,就新建一个事务。

PROPAGATION_MANDATORY:支持当前事务,如果不存在,就抛出异常。

PROPAGATION_SUPPORTS:支持当前事务,如果不存在,就不使用事务。

不支持当前事务(不在同一个事务中):

PROPAGATION_NEVER:以非事务的方式运行,如果有事务,则抛出异常。

PROPAGATION_NOT_SUPPORTED:以非事务的方式运行,如果有事务,则挂起当前事务。

PROPAGATION_REQUIRES_NEW:新建事务,如果有事务,挂起当前事务(两个事务相互独立,父事务回滚不影响子事务)。

PROPAGATION_NESTED:如果当前事务存在,则嵌套事务执行(指必须依存父事务,子事务不能单独提交且父事务回滚则子事务也必须回滚,而子事务若回滚,父事务可以回滚也可以捕获异常)。如果当前没有事务,则进行与PROPAGATION_REQUIRED类似的操作。

别看属性这么多,实际上我们主要用的是PROPAGATION_REQUIRED默认属性,一些特殊业务下可能会用到PROPAGATION_REQUIRES_NEW以及PROPAGATION_NESTED。

下面我会假设一个场景,并主要分析这三个属性。

public class A {

 @Autowired
 private B b;

 @Transactional
 public void addA() {
 b.addB();
 }
}

public class B {
 @Transactional
 public void addB() {
 // doSomething...
 }
}

上面我创建了A、B两个类,每个类中有一个事务方法,使用了声明式事务并采用的默认传播属性,在A中调用了B的方法。

当请求来了调用addA时,首先调用的是代理对象的方法,因此会进入createTransactionIfNecessary方法开启事务:

 protected TransactionInfo createTransactionIfNecessary(@Nullable PlatformTransactionManager tm,
 @Nullable TransactionAttribute txAttr, final String joinpointIdentification) {

 // If no name specified, apply method identification as transaction name.
 if (txAttr != null && txAttr.getName() == null) {
 txAttr = new DelegatingTransactionAttribute(txAttr) {
 @Override
 public String getName() {
  return joinpointIdentification;
 }
 };
 }

 TransactionStatus status = null;
 if (txAttr != null) {
 if (tm != null) {
 //开启事务,这里重点看
 status = tm.getTransaction(txAttr);
 }
 else {
 }
 }
 //创建事务信息对象,记录新老事务信息对象
 return prepareTransactionInfo(tm, txAttr, joinpointIdentification, status);
 }

实际上开启事务是通过AbstractPlatformTransactionManager做的,而这个类是一个抽象类,具体实例化的对象就是我们在项目里常配置的DataSourceTransactionManager对象。

 public final TransactionStatus getTransaction(@Nullable TransactionDefinition definition) throws TransactionException {
 //这里重点看,.DataSourceTransactionObject拿到对象
 Object transaction = doGetTransaction();

 // Cache debug flag to avoid repeated checks.
 boolean debugEnabled = logger.isDebugEnabled();

 if (definition == null) {
 // Use defaults if no transaction definition given.
 definition = new DefaultTransactionDefinition();
 }

 //第一次进来connectionHolder为空的,所以不存在事务
 if (isExistingTransaction(transaction)) {
 // Existing transaction found -> check propagation behavior to find out how to behave.
 return handleExistingTransaction(definition, transaction, debugEnabled);
 }

 // Check definition settings for new transaction.
 if (definition.getTimeout() < TransactionDefinition.TIMEOUT_DEFAULT) {
 throw new InvalidTimeoutException("Invalid transaction timeout", definition.getTimeout());
 }

 // No existing transaction found -> check propagation behavior to find out how to proceed.
 if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_MANDATORY) {
 throw new IllegalTransactionStateException(
  "No existing transaction found for transaction marked with propagation 'mandatory'");
 }
 //第一次进来大部分会走这里
 else if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRED ||
 definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRES_NEW ||
 definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NESTED) {
 //先挂起
 SuspendedResourcesHolder suspendedResources = suspend(null);
 if (debugEnabled) {
  logger.debug("Creating new transaction with name [" + definition.getName() + "]: " + definition);
 }
 try {
  boolean newSynchronization = (getTransactionSynchronization() != SYNCHRONIZATION_NEVER);
  //创建事务状态对象,其实就是封装了事务对象的一些信息,记录事务状态的
  DefaultTransactionStatus status = newTransactionStatus(
  definition, transaction, true, newSynchronization, debugEnabled, suspendedResources);

  //开启事务,重点看看 DataSourceTransactionObject
  doBegin(transaction, definition);

  //开启事务后,改变事务状态
  prepareSynchronization(status, definition);
  return status;
 }
 catch (RuntimeException | Error ex) {
 resume(null, suspendedResources);
 throw ex;
 }
 }
 else {
 boolean newSynchronization = (getTransactionSynchronization() == SYNCHRONIZATION_ALWAYS);
 return prepareTransactionStatus(definition, null, true, newSynchronization, debugEnabled, null);
 }
 }

这个方法流程比较长,一步步来看,先调用doGetTransaction方法获取一个DataSourceTransactionObject对象,这个类是JdbcTransactionObjectSupport的子类,在父类中持有了一个ConnectionHolder对象,见名知意,这个对象保存了当前的连接。

 protected Object doGetTransaction() {
 //管理connection对象,创建回滚点,按照回滚点回滚,释放回滚点
 DataSourceTransactionObject txObject = new DataSourceTransactionObject();

 //DataSourceTransactionManager默认是允许嵌套事务的
 txObject.setSavepointAllowed(isNestedTransactionAllowed());

 //obtainDataSource() 获取数据源对象,其实就是数据库连接块对象
 ConnectionHolder conHolder =
 (ConnectionHolder) TransactionSynchronizationManager.getResource(obtainDataSource());
 txObject.setConnectionHolder(conHolder, false);
 return txObject;
 }

追溯getResource方法可以看到ConnectionHolder 是从ThreadLocal里获取的,也就是当前线程,key是DataSource对象;但是仔细思考下我们是第一次进来,所以这里肯定获取不到的,反之,要从这里获取到值,那必然是同一个线程第二次及以后进入到这里,也就是在addA调用addB时,另外需要注意这里保存ConnectionHolder到DataSourceTransactionObject对象时是将newConnectionHolder属性设置为false了的。

继续往后,创建完transaction对象后,会调用isExistingTransaction判断是否已经存在一个事务,如果存在就会调用handleExistingTransaction方法,这个方法就是处理事务传播的核心方法,因为我们是第一次进来,肯定不存在事务,所以先跳过。

再往后,可以看到就是处理不同的传播属性,主要看到下面这个部分:

 else if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRED ||
 definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRES_NEW ||
 definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NESTED) {
 //先挂起
 SuspendedResourcesHolder suspendedResources = suspend(null);
 if (debugEnabled) {
  logger.debug("Creating new transaction with name [" + definition.getName() + "]: " + definition);
 }
 try {
  boolean newSynchronization = (getTransactionSynchronization() != SYNCHRONIZATION_NEVER);
  //创建事务状态对象,其实就是封装了事务对象的一些信息,记录事务状态的
  DefaultTransactionStatus status = newTransactionStatus(
  definition, transaction, true, newSynchronization, debugEnabled, suspendedResources);

  //开启事务,重点看看 DataSourceTransactionObject
  doBegin(transaction, definition);

  //开启事务后,改变事务状态
  prepareSynchronization(status, definition);
  return status;
 }
 catch (RuntimeException | Error ex) {
 resume(null, suspendedResources);
 throw ex;
 }
 }

第一次进来时,PROPAGATION_REQUIRED、PROPAGATION_REQUIRES_NEW和PROPAGATION_NESTED都会进入到这里,首先会调用suspend挂起当前存在的事务,在这里没啥作用。接下来通过newTransactionStatus创建了DefaultTransactionStatus对象,这个对象主要就是存储当前事务的一些状态信息,需要特别注意newTransaction属性设置为了true,表示是一个新事务。

状态对象创建好之后就是通过doBegin开启事务,这是一个模板方法:

protected void doBegin(Object transaction, TransactionDefinition definition) {
 DataSourceTransactionObject txObject = (DataSourceTransactionObject) transaction;
 Connection con = null;

 try {
 //如果没有数据库连接
 if (!txObject.hasConnectionHolder() ||
  txObject.getConnectionHolder().isSynchronizedWithTransaction()) {
 //从连接池里面获取连接
 Connection newCon = obtainDataSource().getConnection();
 if (logger.isDebugEnabled()) {
  logger.debug("Acquired Connection [" + newCon + "] for JDBC transaction");
 }
 //把连接包装成ConnectionHolder,然后设置到事务对象中
 txObject.setConnectionHolder(new ConnectionHolder(newCon), true);
 }

 txObject.getConnectionHolder().setSynchronizedWithTransaction(true);
 con = txObject.getConnectionHolder().getConnection();

 //从数据库连接中获取隔离级别
 Integer previousIsolationLevel = DataSourceUtils.prepareConnectionForTransaction(con, definition);
 txObject.setPreviousIsolationLevel(previousIsolationLevel);

 // Switch to manual commit if necessary. This is very expensive in some JDBC drivers,
 // so we don't want to do it unnecessarily (for example if we've explicitly
 // configured the connection pool to set it already).
 if (con.getAutoCommit()) {
 txObject.setMustRestoreAutoCommit(true);
 if (logger.isDebugEnabled()) {
  logger.debug("Switching JDBC Connection [" + con + "] to manual commit");
 }
 //关闭连接的自动提交,其实这步就是开启了事务
 con.setAutoCommit(false);
 }

 //设置只读事务 从这一点设置的时间点开始(时间点a)到这个事务结束的过程中,其他事务所提交的数据,该事务将看不见!
 //设置只读事务就是告诉数据库,我这个事务内没有新增,修改,删除操作只有查询操作,不需要数据库锁等操作,减少数据库压力
 prepareTransactionalConnection(con, definition);

 //自动提交关闭了,就说明已经开启事务了,事务是活动的
 txObject.getConnectionHolder().setTransactionActive(true);

 int timeout = determineTimeout(definition);
 if (timeout != TransactionDefinition.TIMEOUT_DEFAULT) {
 txObject.getConnectionHolder().setTimeoutInSeconds(timeout);
 }

 // Bind the connection holder to the thread.
 if (txObject.isNewConnectionHolder()) {
 //如果是新创建的事务,则建立当前线程和数据库连接的关系
 TransactionSynchronizationManager.bindResource(obtainDataSource(), txObject.getConnectionHolder());
 }
 }

 catch (Throwable ex) {
 if (txObject.isNewConnectionHolder()) {
 DataSourceUtils.releaseConnection(con, obtainDataSource());
 txObject.setConnectionHolder(null, false);
 }
 throw new CannotCreateTransactionException("Could not open JDBC Connection for transaction", ex);
 }
 }

这个方法里面主要做了六件事:

首先从连接池获取连接并保存到DataSourceTransactionObject对象中。

关闭数据库的自动提交,也就是开启事务。

获取数据库的隔离级别。

根据属性设置该事务是否为只读事务。

将该事务标识为活动事务(transactionActive=true)。

将ConnectionHolder对象与当前线程绑定。

完成之后通过prepareSynchronization将事务的属性和状态设置到TransactionSynchronizationManager对象中进行管理。最后返回到createTransactionIfNecessary方法中创建TransactionInfo对象与当前线程绑定并返回。

通过以上的步骤就开启了事务,接下来就是通过proceedWithInvocation调用其它切面,这里我们先假设没有其它切面了,那么就是直接调用到A类的addA方法,在这个方法中又调用了B类的addB方法,那么肯定也是调用到代理类的方法,因此又会进入到createTransactionIfNecessary方法中。但这次进来通过isExistingTransaction判断是存在事务的,因此会进入到handleExistingTransaction方法:

 private TransactionStatus handleExistingTransaction(
 TransactionDefinition definition, Object transaction, boolean debugEnabled)
 throws TransactionException {

 //不允许有事务,直接异常
 if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NEVER) {
 throw new IllegalTransactionStateException(
  "Existing transaction found for transaction marked with propagation 'never'");
 }

 //以非事务方式执行操作,如果当前存在事务,就把当前事务挂起
 if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NOT_SUPPORTED) {
 if (debugEnabled) {
 logger.debug("Suspending current transaction");
 }
 //挂起当前事务
 Object suspendedResources = suspend(transaction);
 boolean newSynchronization = (getTransactionSynchronization() == SYNCHRONIZATION_ALWAYS);
 //修改事务状态信息,把事务的一些信息存储到当前线程中,ThreadLocal中
 return prepareTransactionStatus(
  definition, null, false, newSynchronization, debugEnabled, suspendedResources);
 }

 if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRES_NEW) {
 if (debugEnabled) {
 logger.debug("Suspending current transaction, creating new transaction with name [" +
  definition.getName() + "]");
 }
 //挂起
 SuspendedResourcesHolder suspendedResources = suspend(transaction);
 try {
 boolean newSynchronization = (getTransactionSynchronization() != SYNCHRONIZATION_NEVER);
 DefaultTransactionStatus status = newTransactionStatus(
  definition, transaction, true, newSynchronization, debugEnabled, suspendedResources);
 doBegin(transaction, definition);
 prepareSynchronization(status, definition);
 return status;
 }
 catch (RuntimeException | Error beginEx) {
 resumeAfterBeginException(transaction, suspendedResources, beginEx);
 throw beginEx;
 }
 }

 if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NESTED) {
 if (!isNestedTransactionAllowed()) {
 throw new NestedTransactionNotSupportedException(
  "Transaction manager does not allow nested transactions by default - " +
  "specify 'nestedTransactionAllowed' property with value 'true'");
 }
 if (debugEnabled) {
 logger.debug("Creating nested transaction with name [" + definition.getName() + "]");
 }
 //默认是可以嵌套事务的
 if (useSavepointForNestedTransaction()) {
 // Create savepoint within existing Spring-managed transaction,
 // through the SavepointManager API implemented by TransactionStatus.
 // Usually uses JDBC 3.0 savepoints. Never activates Spring synchronization.
 DefaultTransactionStatus status =
  prepareTransactionStatus(definition, transaction, false, false, debugEnabled, null);
 //创建回滚点
 status.createAndHoldSavepoint();
 return status;
 }
 else {
 // Nested transaction through nested begin and commit/rollback calls.
 // Usually only for JTA: Spring synchronization might get activated here
 // in case of a pre-existing JTA transaction.
 boolean newSynchronization = (getTransactionSynchronization() != SYNCHRONIZATION_NEVER);
 DefaultTransactionStatus status = newTransactionStatus(
  definition, transaction, true, newSynchronization, debugEnabled, null);
 doBegin(transaction, definition);
 prepareSynchronization(status, definition);
 return status;
 }
 }

 // 省略

 boolean newSynchronization = (getTransactionSynchronization() != SYNCHRONIZATION_NEVER);
 return prepareTransactionStatus(definition, transaction, false, newSynchronization, debugEnabled, null);
 }

这里面也是对每个传播属性的判断,先看PROPAGATION_REQUIRES_NEW的处理,因为该属性要求每次调用都开启一个新的事务,所以首先会将当前事务挂起,怎么挂起呢?

 protected final SuspendedResourcesHolder suspend(@Nullable Object transaction) throws TransactionException {
 if (TransactionSynchronizationManager.isSynchronizationActive()) {
 List<TransactionSynchronization> suspendedSynchronizations = doSuspendSynchronization();
 try {
 Object suspendedResources = null;
 //第一次进来,肯定为null的
 if (transaction != null) {
  //吧connectionHolder设置为空
  suspendedResources = doSuspend(transaction);
 }

 //做数据还原操作
 String name = TransactionSynchronizationManager.getCurrentTransactionName();
 TransactionSynchronizationManager.setCurrentTransactionName(null);
 boolean readOnly = TransactionSynchronizationManager.isCurrentTransactionReadOnly();
 TransactionSynchronizationManager.setCurrentTransactionReadOnly(false);
 Integer isolationLevel = TransactionSynchronizationManager.getCurrentTransactionIsolationLevel();
 TransactionSynchronizationManager.setCurrentTransactionIsolationLevel(null);
 boolean wasActive = TransactionSynchronizationManager.isActualTransactionActive();
 TransactionSynchronizationManager.setActualTransactionActive(false);
 return new SuspendedResourcesHolder(
  suspendedResources, suspendedSynchronizations, name, readOnly, isolationLevel, wasActive);
 }
 catch (RuntimeException | Error ex) {
 // doSuspend failed - original transaction is still active...
 doResumeSynchronization(suspendedSynchronizations);
 throw ex;
 }
 }
 else if (transaction != null) {
 // Transaction active but no synchronization active.
 Object suspendedResources = doSuspend(transaction);
 return new SuspendedResourcesHolder(suspendedResources);
 }
 else {
 // Neither transaction nor synchronization active.
 return null;
 }
 }

 protected Object doSuspend(Object transaction) {
 DataSourceTransactionObject txObject = (DataSourceTransactionObject) transaction;
 txObject.setConnectionHolder(null);
 //解除绑定关系,
 return TransactionSynchronizationManager.unbindResource(obtainDataSource());
 }

这里明显是进入第一个if并且会调用到doSuspend方法,整体来说挂起事务很简单:首先将DataSourceTransactionObject的ConnectionHolder设置为空并解除与当前线程的绑定,之后将解除绑定的ConnectionHolder和其它属性(事务名称、隔离级别、只读属性)通通封装到SuspendedResourcesHolder对象,并将当前事务的活动状态设置为false。挂起事务之后又通过newTransactionStatus创建了一个新的事务状态并调用doBegin开启事务,这里不再重复分析。

接着来看PROPAGATION_NESTED:

 if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NESTED) {
 if (!isNestedTransactionAllowed()) {
 throw new NestedTransactionNotSupportedException(
  "Transaction manager does not allow nested transactions by default - " +
  "specify 'nestedTransactionAllowed' property with value 'true'");
 }
 if (debugEnabled) {
 logger.debug("Creating nested transaction with name [" + definition.getName() + "]");
 }
 //默认是可以嵌套事务的
 if (useSavepointForNestedTransaction()) {
 // Create savepoint within existing Spring-managed transaction,
 // through the SavepointManager API implemented by TransactionStatus.
 // Usually uses JDBC 3.0 savepoints. Never activates Spring synchronization.
 DefaultTransactionStatus status =
  prepareTransactionStatus(definition, transaction, false, false, debugEnabled, null);
 //创建回滚点
 status.createAndHoldSavepoint();
 return status;
 }
 else {
 // Nested transaction through nested begin and commit/rollback calls.
 // Usually only for JTA: Spring synchronization might get activated here
 // in case of a pre-existing JTA transaction.
 boolean newSynchronization = (getTransactionSynchronization() != SYNCHRONIZATION_NEVER);
 DefaultTransactionStatus status = newTransactionStatus(
  definition, transaction, true, newSynchronization, debugEnabled, null);
 doBegin(transaction, definition);
 prepareSynchronization(status, definition);
 return status;
 }
 }

这里面可以看到如果允许嵌套事务,就会创建一个DefaultTransactionStatus对象(注意newTransaction是false,表明不是一个新事务)和回滚点;如果不允许嵌套,就会创建新事务并开启。

当上面的判断都不满足时,也就是传播属性为默认PROPAGATION_REQUIRED时,则只是创建了一个newTransaction为false的DefaultTransactionStatus返回。

完成之后又是调用proceedWithInvocation,那么就是执行B类的addB方法,假如没有发生异常,那么就会回到切面调用commitTransactionAfterReturning提交addB的事务:

 protected void commitTransactionAfterReturning(@Nullable TransactionInfo txInfo) {
 if (txInfo != null && txInfo.getTransactionStatus() != null) {
 txInfo.getTransactionManager().commit(txInfo.getTransactionStatus());
 }
 }

 public final void commit(TransactionStatus status) throws TransactionException {

 processCommit(defStatus);
 }

 private void processCommit(DefaultTransactionStatus status) throws TransactionException {
 try {
 boolean beforeCompletionInvoked = false;

 try {
 boolean unexpectedRollback = false;
 prepareForCommit(status);
 triggerBeforeCommit(status);
 triggerBeforeCompletion(status);
 beforeCompletionInvoked = true;

 if (status.hasSavepoint()) {
  if (status.isDebug()) {
  logger.debug("Releasing transaction savepoint");
  }
  // 如果是nested,没有提交,只是将savepoint清除掉了
  unexpectedRollback = status.isGlobalRollbackOnly();
  status.releaseHeldSavepoint();
 }
 //如果都是PROPAGATION_REQUIRED,最外层的才会走进来统一提交,如果是PROPAGATION_REQUIRES_NEW,每一个事务都会进来
 else if (status.isNewTransaction()) {
  if (status.isDebug()) {
  logger.debug("Initiating transaction commit");
  }
  unexpectedRollback = status.isGlobalRollbackOnly();
  doCommit(status);
 }
 else if (isFailEarlyOnGlobalRollbackOnly()) {
  unexpectedRollback = status.isGlobalRollbackOnly();
 }
 }
 }
 finally {
 cleanupAfterCompletion(status);
 }
 }

主要逻辑在processCommit方法中。如果存在回滚点,可以看到并没有提交事务,只是将当前事务的回滚点清除了;而如果是新事务,就会调用doCommit提交事务,也就是只有PROPAGATION_REQUIRED属性下的最外层事务和PROPAGATION_REQUIRES_NEW属性下的事务能提交。事务提交完成后会调用cleanupAfterCompletion清除当前事务的状态,如果有挂起的事务还会通过resume恢复挂起的事务(将解绑的连接和当前线程绑定以及将之前保存的事务状态重新设置回去)。当前事务正常提交后,那么就会轮到addA方法提交,处理逻辑同上,不再赘述。

如果调用addB发生异常,就会通过completeTransactionAfterThrowing进行回滚:

 protected void completeTransactionAfterThrowing(@Nullable TransactionInfo txInfo, Throwable ex) {
 if (txInfo != null && txInfo.getTransactionStatus() != null) {
 if (logger.isTraceEnabled()) {
 logger.trace("Completing transaction for [" + txInfo.getJoinpointIdentification() +
  "] after exception: " + ex);
 }
 if (txInfo.transactionAttribute != null && txInfo.transactionAttribute.rollbackOn(ex)) {
 try {
  txInfo.getTransactionManager().rollback(txInfo.getTransactionStatus());
 }
 }
 }
 }

 public final void rollback(TransactionStatus status) throws TransactionException {
 DefaultTransactionStatus defStatus = (DefaultTransactionStatus) status;
 processRollback(defStatus, false);
 }

 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();
 }
 //都为PROPAGATION_REQUIRED最外层事务统一回滚
 else if (status.isNewTransaction()) {
  if (status.isDebug()) {
  logger.debug("Initiating transaction rollback");
  }
  doRollback(status);
 }
 else {
  // Participating in larger transaction
  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");
  }
  // Unexpected rollback only matters here if we're asked to fail early
  if (!isFailEarlyOnGlobalRollbackOnly()) {
  unexpectedRollback = false;
  }
 }
 }
 catch (RuntimeException | Error ex) {
 triggerAfterCompletion(status, TransactionSynchronization.STATUS_UNKNOWN);
 throw ex;
 }

 triggerAfterCompletion(status, TransactionSynchronization.STATUS_ROLLED_BACK);

 // Raise UnexpectedRollbackException if we had a global rollback-only marker
 if (unexpectedRollback) {
 throw new UnexpectedRollbackException(
  "Transaction rolled back because it has been marked as rollback-only");
 }
 }
 finally {
 cleanupAfterCompletion(status);
 }
 }

流程和提交是一样的,先是判断有没有回滚点,如果有就回到到回滚点并清除该回滚点;如果没有则判断是不是新事务(PROPAGATION_REQUIRED属性下的最外层事务和PROPAGATION_REQUIRES_NEW属性下的事务),满足则直接回滚当前事务。回滚完成后同样需要清除掉当前的事务状态并恢复挂起的连接。另外需要特别注意的是在catch里面调用完回滚逻辑后,还通过throw抛出了异常,这意味着什么?意味着即使是嵌套事务,内层事务的回滚也会导致外层事务的回滚,也就是addA的事务也会跟着回滚。

至此,事务的传播原理分析完毕,深入看每个方法的实现是很复杂的,但如果仅仅是分析各个传播属性对事务的影响,则有一个简单的方法。我们可以将内层事务切面等效替换掉invocation.proceedWithInvocation方法,比如上面两个类的调用可以看作是下面这样:

// addA的事务
TransactionInfo txInfo = createTransactionIfNecessary(tm, txAttr, joinpointIdentification);
Object retVal = null;
try {
 // addB的事务
 TransactionInfo txInfo = createTransactionIfNecessary(tm, txAttr, joinpointIdentification);
 Object retVal = null;
 try {
 retVal = invocation.proceedWithInvocation();
 }
 catch (Throwable ex) {
 // target invocation exception
 //事务回滚
 completeTransactionAfterThrowing(txInfo, ex);
 throw ex;
 }
 finally {
 cleanupTransactionInfo(txInfo);
 }
 //事务提交
 commitTransactionAfterReturning(txInfo);
}
catch (Throwable ex) {
 //事务回滚
 completeTransactionAfterThrowing(txInfo, ex);
 throw ex;
}
//事务提交
commitTransactionAfterReturning(txInfo);

这样看是不是很容易就能分析出事务之间的影响以及是提交还是回滚了?下面来看几个实例分析。

实例分析

我再添加一个C类,和addC的方法,然后在addA里面调用这个方法。

TransactionInfo txInfo = createTransactionIfNecessary(tm, txAttr, joinpointIdentification);
Object retVal = null;
try {
 // addB的事务
 TransactionInfo txInfo = createTransactionIfNecessary(tm, txAttr, joinpointIdentification);
 Object retVal = null;
 try {
 b.addB();
 }
 catch (Throwable ex) {
 // target invocation exception
 //事务回滚
 completeTransactionAfterThrowing(txInfo, ex);
 throw ex;
 }
 //事务提交
 commitTransactionAfterReturning(txInfo);

 // addC的事务
 TransactionInfo txInfo = createTransactionIfNecessary(tm, txAttr, joinpointIdentification);
 Object retVal = null;
 try {
 c.addC();
 }
 catch (Throwable ex) {
 // target invocation exception
 //事务回滚
 completeTransactionAfterThrowing(txInfo, ex);
 throw ex;
 }
 //事务提交
 commitTransactionAfterReturning(txInfo);
}
catch (Throwable ex) {
 //事务回滚
 completeTransactionAfterThrowing(txInfo, ex);
 throw ex;
}
//事务提交
commitTransactionAfterReturning(txInfo);

等效替换后就是上面这个代码,我们分别来分析。

都是PROPAGATION_REQUIRED属性:通过上面的分析,我们知道三个方法都是同一个连接和事务,那么任何一个出现异常则都会回滚。

addB为PROPAGATION_REQUIRES_NEW:

如果B中抛出异常,那么B中肯定会回滚,接着异常向上抛,导致A事务整体回滚;

如果C中抛出异常,不难看出C和A都会回滚,但B已经提交了,因此不会受影响。

addC为PROPAGATION_NESTED,addB为PROPAGATION_REQUIRES_NEW:

如果B中抛出异常,那么B回滚并抛出异常,A也回滚,C不会执行;

如果C中抛出异常,先是回滚到回滚点并抛出异常,所以A也回滚,但B此时已经提交,不受影响。

都是PROPAGATION_NESTED:虽然创建了回滚点,但是仍然是同一个连接,任何一个发生异常都会回滚,如果不想影响彼此,可以try-catch生吞子事务的异常实现。

还有其它很多情况,这里就不一一列举了,只要使用上面的分析方法都能够很轻松的分析出来。

总结

本篇详细分析了事务的传播原理,另外还有隔离级别,这在Spring中没有体现,需要我们自己结合数据库的知识进行分析设置。最后我们还需要考虑声明式事务和编程式事务的优缺点,声明式事务虽然简单,但不适合用在长事务中,会占用大量连接资源,这时就需要考虑利用编程式事务的灵活性了。

总而言之,事务的使用并不是一律默认就好,接口的一致性和吞吐量与事务有着直接关系,严重情况下可能会导致系统崩溃。

以上这篇这一次搞懂Spring事务是如何传播的就是小编分享给大家的全部内容了,希望能给大家一个参考,也希望大家多多支持我们。

(0)

相关推荐

  • Spring中的事务管理实例详解

    本文实例讲述了Spring中的事务管理.分享给大家供大家参考.具体分析如下: 事务简介: 事务管理是企业级应用程序开发中必不可少的技术,用来确保数据的完整性和一致性 事务就是一系列的动作,它们被当作一个单独的工作单元.这些动作要么全部完成,要么全部不起作用 事务的四个关键属性(ACID) ① 原子性(atomicity):事务室一个原子操作,有一系列动作组成.事务的原子性确保动作要么全部完成,要么完全不起作用 ② 一致性(consistency):一旦所有事务动作完成,事务就被提交.数据和资源就

  • 这一次搞懂Spring的Bean实例化原理操作

    前言 前两篇文章分析了Spring XML和注解的解析原理,并将其封装为BeanDefinition对象存放到IOC容器中,而这些只是refresh方法中的其中一个步骤--obtainFreshBeanFactory,接下来就将围绕着这些BeanDefinition对象进行一系列的处理,如BeanDefinitionRegistryPostProcessor对象方法的调用.BeanFactoryPostProcessor对象方法的调用以及Bean实例的创建都离不开这些BeanDefinition

  • 详解SpringBoot的事务管理

    Springboot内部提供的事务管理器是根据autoconfigure来进行决定的. 比如当使用jpa的时候,也就是pom中加入了spring-boot-starter-data-jpa这个starter之后. Springboot会构造一个JpaTransactionManager这个事务管理器. 而当我们使用spring-boot-starter-jdbc的时候,构造的事务管理器则是DataSourceTransactionManager. 这2个事务管理器都实现了spring中提供的Pl

  • 详解Springboot事务管理

    在Spring Boot事务管理中,实现自接口PlatformTransactionManager. public interface PlatformTransactionManager { org.springframework.transaction.TransactionStatus getTransaction(org.springframework.transaction.TransactionDefinition transactionDefinition) throws org.

  • 这一次搞懂Spring事务是如何传播的

    前言 上一篇分析了事务注解的解析过程,本质上是将事务封装为切面加入到AOP的执行链中,因此会调用到MethodInceptor的实现类的invoke方法,而事务切面的Interceptor就是TransactionInterceptor,所以本篇直接从该类开始. 正文 事务切面的调用过程 public Object invoke(MethodInvocation invocation) throws Throwable { // Work out the target class: may be

  • 这一次搞懂Spring事务注解的解析方式

    前言 事务我们都知道是什么,而Spring事务就是在数据库之上利用AOP提供声明式事务和编程式事务帮助我们简化开发,解耦业务逻辑和系统逻辑.但是Spring事务原理是怎样?事务在方法间是如何传播的?为什么有时候事务会失效?接下来几篇文章将重点分析Spring事务源码,让我们彻底搞懂Spring事务的原理. 正文 XML标签的解析 <tx:annotation-driven transaction-manager="transactionManager"/> 配置过事务的应该

  • 这一次搞懂Spring的XML解析原理说明

    前言 Spring已经是我们Java Web开发必不可少的一个框架,其大大简化了我们的开发,提高了开发者的效率.同时,其源码对于开发者来说也是宝藏,从中我们可以学习到非常优秀的设计思想以及优雅的命名规范,但因其体系庞大.设计复杂对于刚开始阅读源码的人来说是非常困难的.所以在此之前首先你得下定决心,不管有多困难都得坚持下去:其次,最好先把设计模式掌握熟练:然后在开始阅读源码时一定要多画UML类图和时序图,多问自己为什么要这么设计?这样设计的好处是什么?还有没有更好的设计?当然,晕车是难免的,但还是

  • 这一次搞懂Spring自定义标签以及注解解析原理说明

    前言 在上一篇文章中分析了Spring是如何解析默认标签的,并封装为BeanDefinition注册到缓存中,这一篇就来看看对于像context这种自定义标签是如何解析的.同时我们常用的注解如:@Service.@Component.@Controller标注的类也是需要在xml中配置<context:component-scan>才能自动注入到IOC容器中,所以本篇也会重点分析注解解析原理. 正文 自定义标签解析原理 在上一篇分析默认标签解析时看到过这个类DefaultBeanDefinit

  • 一文搞懂Spring中的Bean作用域

    目录 概述 Singleton prototype request session application 概述 scope用来声明容器中的对象所应该处的限定场景或者说该对象的存活时间,即容器在对象进入其 相应的scope之前,生成并装配这些对象,在该对象不再处于这些scope的限定之后,容器通常会销毁这些对象. Spring容器bean的作用域类型: singleton:Spring IoC 容器的单个对象实例作用域都默认为singleton prototype:针对声明为拥有prototyp

  • 一文搞懂Spring中的注解与反射

    目录 前言 一.内置(常用)注解 1.1@Overrode 1.2@RequestMapping 1.3@RequestBody 1.4@GetMapping 1.5@PathVariable 1.6@RequestParam 1.7@ComponentScan 1.8@Component 1.9@Service 1.10@Repository 二.元注解 @Target @Retention @Documented @Inherited 三.自定义注解 四.反射机制概述 4.1动态语言与静态语

  • 一文搞懂Spring Bean中的作用域和生命周期

    目录 一.Spring Bean 作用域 singleton(单例) prototype(原型) 小结 二.Spring Bean生命周期 如何关闭容器 生命周期回调 通过接口设置生命周期 通过xml设置生命周期 一.Spring Bean 作用域 常规的 Spring IoC 容器中Bean的作用域有两种:singleton(单例)和prototype(非单例) 注:基于Web的容器还有其他种作用域,在这就不赘述了. singleton(单例) singleton是Spring默认的作用域.当

  • 一文搞懂Spring中Bean的生命周期

    目录 一.使用配置生命周期的方法 二.生命周期控制——接口控制(了解) 小结 生命周期:从创建到消亡的完整过程 bean声明周期:bean从创建到销毁的整体过程 bean声明周期控制:在bean创建后到销毁前做一些事情 一.使用配置生命周期的方法 在BookDaoImpl中实现类中创建相应的方法: //表示bean初始化对应的操作 public void init(){ System.out.println("init..."); } //表示bean销毁前对应的操作 public v

  • 一文搞懂Spring循环依赖的原理

    目录 简介 循环依赖实例 测试 简介 说明 本文用实例来介绍@Autowired解决循环依赖的原理.@Autowired是通过三级缓存来解决循环依赖的. 除了@Autoired,还有其他方案来解决循环依赖的,见:Spring循环依赖的解决方案详解 概述 @Autowired进行属性注入可以解决循环依赖.原理是:Spring控制了bean的生命周期,先实例化bean,后注入bean的属性.Spring中记录了正在创建中的bean(已经实例化但还没初始化完毕的bean),所以可以在注入属性时,从记录

  • 一文带你搞懂Spring响应式编程

    目录 1. 前言 1.1 常用函数式编程 1.2 Stream操作 2. Java响应式编程 带有中间处理器的响应式流 3. Reactor 3.1 Flux & Mono 3.2 Flux Mono创建与使用 4. WebFlux Spring WebFlux示例 基于注解的WebFlux 基于函数式编程的WebFlux Flux与Mono的响应式编程延迟示例 总结 哈喽,大家好,我是指北君. 相信响应式编程经常会在各种地方被提到.本篇就为大家从函数式编程一直到Spring WeFlux做一次

随机推荐