The problem background

Database transactions are heavily used in most business scenarios. We know that Spring provides the @Transactional annotation to support database transactions. In what cases does this annotation take effect? The following is a focus on the source code

First look at the definition of Transactional

@Target({ElementType.METHOD, Elementtype.type}) public @interface Transactional {// Transactional Transactional () // Isolation Isolation () default Isolation.default; /** ** ** ** / Class<? extends Throwable>[] rollbackFor() default {}; String[] rollbackForClassName() default {}; Class<? extends Throwable>[] noRollbackFor() default {}; String[] noRollbackForClassName() default {};Copy the code

@ Transactional database transaction is based on AOP technology implementation, Propagation is defines how to use the database transaction, seven kinds of Propagation in the Spring transaction attributes of a class a:

  • REQUIRED: Supports the current transaction and creates a new one if there are none. This is the most common choice.

  • SUPPORTS: SUPPORTS the current transaction. If there is no transaction, it will be executed in a non-transactional manner.

  • MANDATORY: The current transaction is supported. If there is no transaction, an exception is thrown.

  • REQUIRES_NEW: Creates a new transaction and suspends the current one if one exists.

  • NOT_SUPPORTED: Performs an operation in a non-transactional manner and suspends the current transaction if one exists.

  • NEVER: executes non-transactionally and throws an exception if a transaction currently exists.

  • NESTED: Supports the current transaction. If the current transaction exists, a NESTED transaction is executed. If no transaction exists, a new one is created.

AnnotationTransactionAttributeSource

Inherited from AbstractFallbackTransactionAttributeSource TransactionAttributeSource and interface

@Nullable public TransactionAttribute getTransactionAttribute(Method method, @Nullable Class<? > targetClass) { if (method.getDeclaringClass() == Object.class) { return null; } Object cacheKey = getCacheKey(method, targetClass); TransactionAttribute cached = this.attributeCache.get(cacheKey); if (cached ! = null) { if (cached == NULL_TRANSACTION_ATTRIBUTE) { return null; }else { return cached; } }else { TransactionAttribute txAttr = computeTransactionAttribute(method, targetClass); if (txAttr == null) { this.attributeCache.put(cacheKey, NULL_TRANSACTION_ATTRIBUTE); }else { String methodIdentification = ClassUtils.getQualifiedMethodName(method, targetClass); if (txAttr instanceof DefaultTransactionAttribute) { ((DefaultTransactionAttribute) txAttr).setDescriptor(methodIdentification); } if (logger.isDebugEnabled()) { logger.debug("Adding transactional method '" + methodIdentification + "' with attribute: " + txAttr); } this.attributeCache.put(cacheKey, txAttr); } return txAttr; } } @Nullable protected TransactionAttribute computeTransactionAttribute(Method method, @Nullable Class<? > targetClass) { if (allowPublicMethodsOnly() && ! Modifier. IsPublic (method.getModiFIERS ())) {mysql.ispublic (method.getModifiers())) {Modifier must be public return null; } Method specificMethod = AopUtils.*getMostSpecificMethod*(method, targetClass); TransactionAttribute txAttr = findTransactionAttribute(specificMethod); if (txAttr ! = null) { return txAttr; } txAttr = findTransactionAttribute(specificMethod.getDeclaringClass()); if (txAttr ! = null && ClassUtils.*isUserLevelMethod*(method)) { return txAttr; } if (specificMethod ! = method) { txAttr = findTransactionAttribute(method); if (txAttr ! = **null**) { return txAttr; } txAttr = findTransactionAttribute(method.getDeclaringClass()); if (txAttr ! = **null** && ClassUtils.*isUserLevelMethod*(method)) { return txAttr; } } return** null; }Copy the code
TransactionAspectSupport.class
private static final ThreadLocal<TransactionInfo> transactionInfoHolder = new NamedThreadLocal<>("Current aspect-driven transaction"); / / transaction management @ Nullable private PlatformTransactionManager transactionManager; / / transaction attribute configuration @ Nullable private TransactionAttributeSource TransactionAttributeSource; @Nullable private BeanFactory beanFactory; Private final ConcurrentMap<Object, PlatformTransactionManager> transactionManagerCache = new ConcurrentReferenceHashMap<>(4);Copy the code
@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.
    // 获取方法定义的 Transaction 事物相关的属性
    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)) {
    // 新建 TransactionInfo 对象
        TransactionInfo txInfo = createTransactionIfNecessary(tm, txAttr, joinpointIdentification);
        Object retVal = null

        try {
            // 执行切入
            retVal = invocation.proceedWithInvocation();
            }catch (Throwable ex) {
            // 异常处理
                completeTransactionAfterThrowing(txInfo, ex);
                throw ex;
            }finally {
                // 清除缓存信息
                cleanupTransactionInfo(txInfo);
            }
            //执行 Commit
            commitTransactionAfterReturning(txInfo);
            return retVal;
        }
    else {
        final ThrowableHolder throwableHolder = new ThrowableHolder();
        try {
            Object result = ((CallbackPreferringPlatformTransactionManager) tm).execute(txAttr, status -> {
            TransactionInfo txInfo = prepareTransactionInfo(tm, txAttr, joinpointIdentification, status);
            try {
                return invocation.proceedWithInvocation();
            }catch(Throwable ex) {
                // 自定义rallback异常处理
                if (txAttr.rollbackOn(ex)) {
                    if (ex instanceof RuntimeException) {
                        throw (RuntimeException) ex;
                    }else{
                        throw new ThrowableHolderException(ex);
                    }
                } else {
                        throwableHolder.throwable = ex;
                        return  null;
                }
            }finally{
                cleanupTransactionInfo(txInfo);
            }});
            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;
       }
   }
}

/**
* 获取对应transaction的Manager
*/
@Nullable
protected PlatformTransactionManager determineTransactionManager(@Nullable TransactionAttribute txAttr) {
    if (txAttr == null || this.beanFactory == null) {
        return getTransactionManager();
    }
    //. 按照 qualifier 超找 manager
    String qualifier = txAttr.getQualifier();

    if (StringUtils.hasText(qualifier)) {
        return determineQualifiedTransactionManager(this.beanFactory, qualifier);
    }else if (StringUtils.hasText(this.transactionManagerBeanName)) {
        return determineQualifiedTransactionManager(**this**.beanFactory, this.transactionManagerBeanName);
    }else {
         // 创建Manager 并缓存
        PlatformTransactionManager defaultTransactionManager = getTransactionManager();
        if (defaultTransactionManager == null) {
            defaultTransactionManager = this.transactionManagerCache.get(DEFAULT_TRANSACTION_MANAGER_KEY);
            if (defaultTransactionManager == null) {
                defaultTransactionManager = this.beanFactory.getBean(PlatformTransactionManager.class);
                this.transactionManagerCache.putIfAbsent(
DEFAULT_TRANSACTION_MANAGER_KEY, defaultTransactionManager);
            }
        }
        return defaultTransactionManager;
   }
}

// 获取方法名称
private String methodIdentification(Method method, @Nullable Class<?> targetClass,@Nullable TransactionAttribute txAttr) {
    String methodIdentification = methodIdentification(method, targetClass);
    if (methodIdentification == null) {
        if (txAttr instanceof DefaultTransactionAttribute) {
            methodIdentification = ((DefaultTransactionAttribute) txAttr).getDescriptor();
        }
        if (methodIdentification == null) {
            methodIdentification = ClassUtils.*getQualifiedMethodName*(method, targetClass);
        }
    }
    return methodIdentification;

}

// TransactionAttribute 生成对应的 TransactionInfo 此处为线程级别的TransactionInfo
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 {
            if (logger.isDebugEnabled()) {
                logger.debug("Skipping transactional joinpoint [" + joinpointIdentification +"] because no transaction manager has been configured");
           }
        }
    }
    return prepareTransactionInfo(tm, txAttr, joinpointIdentification, status);
}

// 生成TransactionInfo,并维护status
protected TransactionInfo prepareTransactionInfo(@Nullable PlatformTransactionManager tm,@Nullable TransactionAttribute txAttr, String joinpointIdentification,@Nullable TransactionStatus status) {
    TransactionInfo txInfo = new TransactionInfo(tm, txAttr, joinpointIdentification);
    if (txAttr != null) {
        if (logger.isTraceEnabled()) {
            logger.trace("Getting transaction for [" + txInfo.getJoinpointIdentification() + "]");
        }
        txInfo.newTransactionStatus(status);
    }else {
        if (logger.isTraceEnabled())
            logger.trace("Don't need to create transaction for [" + joinpointIdentification +"]: This method isn't transactional.");
    }
    txInfo.bindToThread();
    return txInfo;
}

protected void cleanupTransactionInfo(@Nullable TransactionInfo txInfo) {
    if (txInfo != null) {
        txInfo.restoreThreadLocalStatus();
    }
}

private void restoreThreadLocalStatus() {
    transactionInfoHolder.set(this.oldTransactionInfo);
}

protected void commitTransactionAfterReturning(@Nullable TransactionInfo txInfo) {
    if (txInfo != null && txInfo.getTransactionStatus() != null) {
        if (logger.isTraceEnabled()) {
            logger.trace("Completing transaction for [" +txInfo.getJoinpointIdentification() + "]");
        }
       //此处才是真正 执行 commit txInfo.getTransactionManager().commit(txInfo.getTransactionStatus());
     }
}
Copy the code
AbstractPlatformTransactionManager.class

In the actual Jdbc DataSource DataSourceTransactionManager is realized

@Override public final void commit(TransactionStatus Status) throws TransactionException {// Check the status 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 ()) {/ / processing Rollback if (defStatus. IsDebug ()) {logger. The 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; } // Actually execute commit processCommit(defStatus); } private void processRollback(DefaultTransactionStatus status, boolean unexpected) { try { boolean unexpectedRollback = unexpected; Try {// Life cycle rollback before beginning 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"); } // The actual rollback triggers doRollback(status); }else { 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; } / / life cycle after the rollback start triggerAfterCompletion STATUS_ROLLED_BACK (status, TransactionSynchronization. * * * * * *); // 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); }} // Clean up resources, Private void cleanupAfterCompletion(DefaultTransactionStatus status) {status.setCompleted(); Clean up if / / tm (status. IsNewSynchronization ()) {TransactionSynchronizationManager. The clear (); } if (status.isnewTransaction ()) {doCleanupAfterCompletion(status.getTransaction()); } if (status.getSuspendedResources() ! = null) {// Handle interrupt recovery if (status.isdebug ()) {logger.debug(" output suspended transaction after completion of inner transaction"); } Object transaction = (status.hasTransaction() ? status.getTransaction() : null); resume(transaction, (SuspendedResourcesHolder) status.getSuspendedResources()); }} // commit 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"); } unexpectedRollback = status.isGlobalRollbackOnly(); status.releaseHeldSavepoint(); }else if (status.isNewTransaction()) { if (status.isDebug()) { logger.debug("Initiating transaction commit"); } unexpectedRollback = status.isGlobalRollbackOnly(); // Implement doCommit(status) by subclass; }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) { triggerAfterCompletion(status, TransactionSynchronization.STATUS_ROLLED_BACK); throw ex; }catch (TransactionException ex) { if (isRollbackOnCommitFailure()) { doRollbackOnCommitException(status, ex); }else { triggerAfterCompletion(status, TransactionSynchronization.STATUS_UNKNOWN); } throw ex; }catch (RuntimeException | Error ex) { if (! beforeCompletionInvoked) { triggerBeforeCompletion(status); } / / rollback processing, eventually doRollbackOnCommitException doRollback by a subclass implementation method (status, the ex); throw ex; } try { triggerAfterCommit(status); }finally { triggerAfterCompletion(status, TransactionSynchronization.STATUS_COMMITTED); } }finally { cleanupAfterCompletion(status); }}Copy the code
TransactionSynchronizationUtils. Class help util class
public static void triggerBeforeCommit(boolean readOnly)
public static void triggerBeforeCompletion();
public static void triggerAfterCommit();
public static void invokeAfterCommit(@Nullable List<TransactionSynchronization> synchronizations)
public static void triggerAfterCompletion(int completionStatus)
public static void invokeAfterCompletion(@Nullable List<TransactionSynchronization> synchronizations,int completionStatus)
Copy the code
DataSourceTransactionManager database related TM
protected void doBegin(Object transaction, TransactionDefinition definition)
protected Object doSuspend(Object transaction)
protected void doResume(@Nullable Object transaction, Object suspendedResources)
protected void doCommit(DefaultTransactionStatus status)
protected void doRollback(DefaultTransactionStatus status)
protected void doSetRollbackOnly(DefaultTransactionStatus status)
protected void doCleanupAfterCompletion(Object transaction)
Copy the code

Transactional use scenario

  1. @Transactional is based on the Java AOP mechanism, and all methods for final modifications do not work because methods cannot generate proxy classes through cglib, AOP, etc
  2. Methods modified by @Transactional must be public. No other method can generate a transaction. The main reason for this is the visibility of inheritance
  3. The class of the @Transactional modified method must be a Spring-managed Bean object; Because Transaction transactions use the Bean’s life cycle
  4. @Transactional does not support multithreading because the transaction is bound to the DataSource’s database connection. Different threads have different database connection instances. ThreadLocal management connect

The @Transactional transaction rollback summary

  1. New transactions are currently created only with these three propagation features: REQUIRED, REQUIRES_NEW, and NESTED. The others do not support transactions and throw exceptions
  2. The rollback of transactions is based on the handling of exceptions. In the rollback of all things, exceptions need to be caught so that the user code should not handle exceptions. Once exceptions are handled, the transaction Manager will not be aware of them
  3. The user-defined ROLLBACK exceptions must be inherited based on the actual situation. Otherwise, the exceptions cannot be captured during rollback and the rollback is invalid
  4. Of particular concern are when there are multiple methods in a transaction, or when there is nesting, excessive rollback needs to be prevented, and exceptions need to be handled directly at the nesting layer. No longer notifies the upper transaction for rollback operations; This mechanism is called savePoint. Note the granularity of rollback in the case of nested use

Finally, @Transactional is extremely easy to use and lightweight, but problems come up that are sometimes particularly difficult to locate. So use caution.