Springboot source code analysis of the affairs issues

Summary:

Services in the back-end development everywhere, is essential to ensure data consistency. To understand the nature of the transaction is to enter into a transaction proxy method section, the most common method is to call a class with non-transaction of a transaction plus a method annotated did not go into business. Our cglibagency, for example, due to the Spring for cglib AOPproxy implementations, enter the proxy method, when in fact has left the "agency this layer shell", the code can think of is come to a simple bean, call the same bean not a cent in the methods of natural relationship with the agency.
Plus a general statement for the transaction is to call another class @Transactionalannotated publicas a method of entry.

Transaction processing flow of the key spring

  • EnableTransactionManagementImport NotesTransactionManagementConfigurationSelector
  • TransactionManagementConfigurationSelectorLoad InfrastructureAdvisorAutoProxyCreator(but not necessarily it, usually AnnotationAwareAspectJAutoProxyCreator), BeanFactoryTransactionAttributeSourceAdvisor, TransactionInterceptor
    - AnnotationAwareAspectJAutoProxyCreatorin iocthe process is a crucial step to find Advisor, there are two aspects, the first is to achieve a Advisorclass interface, and the second is based on the notes Aspectj. The key is to BeanFactoryTransactionAttributeSourceAdvisorbe loaded into the proxy cache
  • When the agent calls the method performs DefaultAdvisorChainFactory#getInterceptorsAndDynamicInterceptionAdvice, this time will be our

BeanFactoryTransactionAttributeSourceAdvisorPut to use, the most important thing in it's TransactionAttributeSourcePointcutmatch, execution TransactionInterceptormethod

TransactionInterceptor

    @Override
    @Nullable
    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);
    }

TransactionAspectSupport

    @Nullable
    protected Object invokeWithinTransaction(Method method, @Nullable Class<?> targetClass,
          final InvocationCallback invocation) throws Throwable {
    
       // If the transaction attribute is null, the method is non-transactional.
       TransactionAttributeSource tas = getTransactionAttributeSource();
       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)) {
          // Standard transaction demarcation with getTransaction and commit/rollback calls.
          TransactionInfo txInfo = createTransactionIfNecessary(tm, txAttr, joinpointIdentification);
    
          Object retVal;
          try {
             // This is an around advice: Invoke the next interceptor in the chain.
             // This will normally result in a target object being invoked.
             retVal = invocation.proceedWithInvocation();
          }
          catch (Throwable ex) {
             // target invocation exception
             completeTransactionAfterThrowing(txInfo, ex);
             throw ex;
          }
          finally {
             cleanupTransactionInfo(txInfo);
          }
          commitTransactionAfterReturning(txInfo);
          return retVal;
       }
    
       else {
          final ThrowableHolder throwableHolder = new ThrowableHolder();
    
          // It's a CallbackPreferringPlatformTransactionManager: pass a TransactionCallback in.
          try {
             Object result = ((CallbackPreferringPlatformTransactionManager) tm).execute(txAttr, status -> {
                TransactionInfo txInfo = prepareTransactionInfo(tm, txAttr, joinpointIdentification, status);
                try {
                   return invocation.proceedWithInvocation();
                }
                catch (Throwable ex) {
                   if (txAttr.rollbackOn(ex)) {
                      // A RuntimeException: will lead to a rollback.
                      if (ex instanceof RuntimeException) {
                         throw (RuntimeException) ex;
                      }
                      else {
                         throw new ThrowableHolderException(ex);
                      }
                   }
                   else {
                      // A normal return value: will lead to a commit.
                      throwableHolder.throwable = ex;
                      return null;
                   }
                }
                finally {
                   cleanupTransactionInfo(txInfo);
                }
             });
    
             // Check result state: It might indicate a Throwable to rethrow.
             if (throwableHolder.throwable != null) {
                throw throwableHolder.throwable;
             }
             return result;
          }
          catch (ThrowableHolderException ex) {
             throw ex.getCause();
          }
          catch (TransactionSystemException ex2) {
             if (throwableHolder.throwable != null) {
                logger.error("Application exception overridden by commit exception", throwableHolder.throwable);
                ex2.initApplicationException(throwableHolder.throwable);
             }
             throw ex2;
          }
          catch (Throwable ex2) {
             if (throwableHolder.throwable != null) {
                logger.error("Application exception overridden by commit exception", throwableHolder.throwable);
             }
             throw ex2;
          }
       }
    }

The analysis in this method, but from abnormal transaction, does not take effect perspectives to analyze the problem. Notes Affairs and programmatic same core idea, let's analyze the business logic comment

    if (txAttr == null || !(tm instanceof CallbackPreferringPlatformTransactionManager)) {
                // Standard transaction demarcation with getTransaction and commit/rollback calls.
                TransactionInfo txInfo = createTransactionIfNecessary(tm, txAttr, joinpointIdentification);
    
                Object retVal;
                try {
                    // This is an around advice: Invoke the next interceptor in the chain.
                    // This will normally result in a target object being invoked.
                    retVal = invocation.proceedWithInvocation();
                }
                catch (Throwable ex) {
                    // target invocation exception
                    completeTransactionAfterThrowing(txInfo, ex);
                    throw ex;
                }
                finally {
            // 把上一层事务的TxInfo重新绑到ThreadLocal中
                    cleanupTransactionInfo(txInfo);
                }
                commitTransactionAfterReturning(txInfo);
                return retVal;
            }

Remember these core methods in a logical order and abnormal capture Oh!

    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());
                    }
                    catch (TransactionSystemException ex2) {
                        logger.error("Application exception overridden by rollback exception", ex);
                        ex2.initApplicationException(ex);
                        throw ex2;
                    }
                    catch (RuntimeException | Error ex2) {
                        logger.error("Application exception overridden by rollback exception", ex);
                        throw ex2;
                    }
                }
                else {
                    // We don't roll back on this exception.
                    // Will still roll back if TransactionStatus.isRollbackOnly() is true.
                    try {
                        txInfo.getTransactionManager().commit(txInfo.getTransactionStatus());
                    }
                    catch (TransactionSystemException ex2) {
                        logger.error("Application exception overridden by commit exception", ex);
                        ex2.initApplicationException(ex);
                        throw ex2;
                    }
                    catch (RuntimeException | Error ex2) {
                        logger.error("Application exception overridden by commit exception", ex);
                        throw ex2;
                    }
                }
            }
        }

Abnormal supports transaction rollback

    @Override
    public boolean rollbackOn(Throwable ex) {
       return (ex instanceof RuntimeException || ex instanceof Error);
    }

Note that point, the only exception and error support mechanisms is running, or not rolled back. And direct conditions.

AbstractPlatformTransactionManager

    private void processRollback(DefaultTransactionStatus status, boolean unexpected) {
       try {
         //默认false
          boolean unexpectedRollback = unexpected;
          try {
            //回调TransactionSynchronization对象的beforeCompletion方法。
             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");
                }
               // 由具体TxMgr子类实现回滚。
                doRollback(status);
             }
             else {
                // Participating in larger transaction
                if (status.hasTransaction()) {
                  /*
                     * 内层事务被标记为rollBackOnly或者globalRollbackOnParticipationFailure开关开启时,给当前事务标记需要回滚。
                     * 
                     * 如果内层事务显式打上了rollBackOnly的标记,最终全事务一定是回滚掉的。
                     * 
                     * 但如果没有被打上rollBackOnly标记,则globalRollbackOnParticipationFailure开关就很重要了。
                     * globalRollbackOnParticipationFailure开关默认是开启的,也就是说内层事务挂了,最终的结果只能是全事务回滚。
                     * 但如果globalRollbackOnParticipationFailure开关被关闭的话,内层事务挂了,外层事务业务方法中可以根据情况控制是否回滚。
                     */
     
                   if (status.isLocalRollbackOnly() || isGlobalRollbackOnParticipationFailure()) {
                      if (status.isDebug()) {
                         logger.debug("Participating transaction failed - marking existing transaction as rollback-only");
                      }
                     // 由具体TxMgr子类实现回滚。
                      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;
          }
    // 回调TransactionSynchronization对象的afterCompletion方法。
          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);
       }
    }

case study

file

file

Experienced students must know eventually the entire transaction is rolled back out, TransactionB#testand did not execute System.out.println("TransactionB#test after");
in fact, for the Spring transaction, such a result is correct, but for developers, this result may seem really "can not understand."

We wish to analyze the reasons:

First of TransactionB#testitself is a direct throw RuntimeException, then retire after the transaction stack to cut, cut transaction rollback, but find that you need because TransactionB#testnot the outermost border transactions, so the AbstractPlatformTransactionManager#processRollbackmethod is only invoked doSetRollbackOnly(status); subclasses DataSourceTransactionManagerwill come up with DefaultTransactionStatusthe transactionobjects marked rollback marks, specifically, transactionthe object (for DataSourceTransactionManagerit is a type DataSourceTransactionObject) will be removed ConnectionHolder, call setRollbackOnly. We know that this is equivalent to a global mark is labeled, as long as the same physical transaction is part of the Spring transaction can read the same ConnectionHolder.

    protected void doSetRollbackOnly(DefaultTransactionStatus status) {
            DataSourceTransactionManager.DataSourceTransactionObject txObject = (DataSourceTransactionManager.DataSourceTransactionObject)status.getTransaction();
            if (status.isDebug()) {
                this.logger.debug("Setting JDBC transaction [" + txObject.getConnectionHolder().getConnection() + "] rollback-only");
            }
      //关键点
            txObject.setRollbackOnly();
        }

Back to the top affairs section, in AbstractPlatformTransactionManager#committhe method read if(!shouldCommitOnGlobalRollbackOnly() && defStatus.isGlobalRollbackOnly())condition is satisfied, the next call processRollback, due to the rollback in the transaction outermost boundary of physics will fall, and it is also the outermost boundary of the transaction, Spring thrown UnexpectedRollbackException.

How to solve?

So how to solve the problem then, this problem has several solutions, but have to decide depending on the circumstances.

  • According to the actual code and business case process, if the embedded annotation cancel the transaction, Spring also does not throw UnexpectedRollbackException. But the method does not actually complete the implementation, so this solution ideas easily lead to incomplete stale data.

  • Manual control if rolled back. If this is unacceptable, then hang nested transactions can be added in the catch block TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();for rollback explicit control. This Spring will understand your own requirements roll back the transaction, rather than the unexpected. Spring also does not throw UnexpectedRollbackExceptionup. So if Exception caught in the upper affairs, really I did not want to roll back, even if an abnormality occurs in the upper affairs, also want to eventually submit the entire transaction it? If there is such a demand, you can configure a parameter to the transaction managersetGlobalRollbackOnParticipationFailure(false);

  • If isGlobalRollbackOnParticipationFailure is false, then make the decision to roll back the main transaction, if the transaction fails when faced with exception join, the caller can continue to decide whether to roll back or continue in business. Note, however, that do apply only in the case of data access failure and as long as all the operations in the transaction can be committed, this method can solve, but apparently affect the global transaction attributes, so strongly not recommended.

    public final void commit(TransactionStatus status) throws TransactionException {
       if (status.isCompleted()) {
          throw new IllegalTransactionStateException(
                "Transaction is already completed - do not call commit or rollback more than once per transaction");
       }
    
       DefaultTransactionStatus defStatus = (DefaultTransactionStatus) status;
       if (defStatus.isLocalRollbackOnly()) {
          if (defStatus.isDebug()) {
             logger.debug("Transactional code has requested rollback");
          }
          processRollback(defStatus, false);
          return;
       }
    
       if (!shouldCommitOnGlobalRollbackOnly() && defStatus.isGlobalRollbackOnly()) {
          if (defStatus.isDebug()) {
             logger.debug("Global transaction is marked as rollback-only but transactional code requested commit");
          }
          processRollback(defStatus, true);
          return;
       }
    
       processCommit(defStatus);
    }

Guess you like

Origin www.cnblogs.com/qinzj/p/11456067.html