Spring事務專題(五)聊聊Spring事務到底是如何實現的

程式設計師DMZ發表於2020-08-16

前言

本專題大綱:

image-20200809182052520

本文為本專題倒數第二篇文章。

上篇文章中我們一起學習了Spring中的事務抽象機制以及動手模擬了一下Spring中的事務管理機制,那麼本文我們就通過原始碼來分析一下Spring中的事務管理到底是如何實現的,本文將選用Spring5.2.x版本。

從@EnableTransactionManagement開始

Spring事務管理的入口就是@EnableTransactionManagement註解,所以我們直接從這個註解入手,其原始碼如下:

public @interface EnableTransactionManagement {
	
	// 是否使用cglib代理,預設是jdk代理
	boolean proxyTargetClass() default false;
	
    // 使用哪種代理模式,Spring AOP還是AspectJ
	AdviceMode mode() default AdviceMode.PROXY;
	
    // 為了完成事務管理,會向容器中新增通知
    // 這個order屬性代表了通知的執行優先順序
    // 預設是最低優先順序
	int order() default Ordered.LOWEST_PRECEDENCE;

}

需要注意的是,@EnableTransactionManagementproxyTargetClass會影響Spring中所有通過自動代理生成的物件。如果將proxyTargetClass設定為true,那麼意味通過@EnableAspectJAutoProxy所生成的代理物件也會使用cglib進行代理。關於@EnableTransactionManagement@EnableAspectJAutoProxy混用時的一些問題等我們在對@EnableTransactionManagement有一定了解後再專門做一個比較,現在我們先來看看這個註解到底在做了什麼?

事務註解分析

從上圖中可以看出這個註解做的就是向容器中註冊了AutoProxyRegistrar跟一個ProxyTransactionManagementConfiguration這裡就不考慮AspectJ了,我們平常都是使用SpringAOP),

AutoProxyRegistrar用於開啟自動代理,其原始碼如下:

AutoProxyRegistrar分析

這個類實現了ImportBeanDefinitionRegistrar,它的作用是向容器中註冊別的BeanDefinition,我們直接關注它的registerBeanDefinitions方法即可

// 	AnnotationMetadata,代表的是AutoProxyRegistrar的匯入類的元資訊
// 既包含了類元資訊,也包含了註解元資訊
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
    boolean candidateFound = false;
    // 獲取@EnableTransactionManagement所在配置類上的註解元資訊
    Set<String> annTypes = importingClassMetadata.getAnnotationTypes();
    // 遍歷註解
    for (String annType : annTypes) {
        // 可以理解為將註解中的屬性轉換成一個map
        AnnotationAttributes candidate = AnnotationConfigUtils.attributesFor(importingClassMetadata, annType);
        if (candidate == null) {
            continue;
        }
        // 直接從map中獲取對應的屬性
        Object mode = candidate.get("mode");
        Object proxyTargetClass = candidate.get("proxyTargetClass");

        // mode,代理模型,一般都是SpringAOP
        // proxyTargetClass,是否使用cglib代理
        if (mode != null && proxyTargetClass != null && AdviceMode.class == mode.getClass() &&
            Boolean.class == proxyTargetClass.getClass()) {

            // 註解中存在這兩個屬性,並且屬性型別符合要求,表示找到了合適的註解
            candidateFound = true;

            // 實際上會往容器中註冊一個InfrastructureAdvisorAutoProxyCreator
            if (mode == AdviceMode.PROXY) {
                AopConfigUtils.registerAutoProxyCreatorIfNecessary(registry);
                if ((Boolean) proxyTargetClass) {
                    AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry);
                    return;
                }
            }
        }
    }
    // ......
}

@EnableTransactionManagement跟@EnableAspectJAutoProxy

如果對AOP比較瞭解的話,那麼應該知道@EnableAspectJAutoProxy註解也向容器中註冊了一個能實現自動代理的bd,那麼當@EnableAspectJAutoProxy@EnableTransactionManagement同時使用會有什麼問題嗎?答案大家肯定知道,不會有問題,那麼為什麼呢?我們檢視原始碼會發現,@EnableAspectJAutoProxy最終呼叫的是

AopConfigUtils#registerAspectJAnnotationAutoProxyCreatorIfNecessary,其原始碼如下

public static BeanDefinition registerAspectJAnnotationAutoProxyCreatorIfNecessary(
    BeanDefinitionRegistry registry, @Nullable Object source) {
    return registerOrEscalateApcAsRequired(AnnotationAwareAspectJAutoProxyCreator.class, registry, source);
}

@EnableTransactionManagement最終呼叫的是,AopConfigUtils#registerAutoProxyCreatorIfNecessary,其原始碼如下

public static BeanDefinition registerAutoProxyCreatorIfNecessary(
    BeanDefinitionRegistry registry, @Nullable Object source) {
    return registerOrEscalateApcAsRequired(InfrastructureAdvisorAutoProxyCreator.class, registry, source);
}

它們最終都會呼叫registerOrEscalateApcAsRequired方法,只不過傳入的引數不一樣而已,一個是AnnotationAwareAspectJAutoProxyCreator,另一個是InfrastructureAdvisorAutoProxyCreator

registerOrEscalateApcAsRequired原始碼如下:

private static BeanDefinition registerOrEscalateApcAsRequired(
    Class<?> cls, BeanDefinitionRegistry registry, @Nullable Object source) {
    if (registry.containsBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME)) {
        BeanDefinition apcDefinition = registry.getBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME);
        if (!cls.getName().equals(apcDefinition.getBeanClassName())) {
            // 當前已經註冊到容器中的Bean的優先順序
            int currentPriority = findPriorityForClass(apcDefinition.getBeanClassName());
            // 當前準備註冊到容器中的Bean的優先順序
            int requiredPriority = findPriorityForClass(cls);
            // 誰的優先順序大就註冊誰,AnnotationAwareAspectJAutoProxyCreator是最大的
            // 所以AnnotationAwareAspectJAutoProxyCreator會覆蓋別的Bean
            if (currentPriority < requiredPriority) {
                apcDefinition.setBeanClassName(cls.getName());
            }
        }
        return null;
    }
	// 註冊bd
    RootBeanDefinition beanDefinition = new RootBeanDefinition(cls);
    beanDefinition.setSource(source);
    beanDefinition.getPropertyValues().add("order", Ordered.HIGHEST_PRECEDENCE);
    beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
    registry.registerBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME, beanDefinition);
    return beanDefinition;
}

InfrastructureAdvisorAutoProxyCreatorAnnotationAwareAspectJAutoProxyCreator的優先順序是如何定義的呢?我們來看看AopConfigUtils這個類中的一個靜態程式碼塊

static {
    APC_PRIORITY_LIST.add(InfrastructureAdvisorAutoProxyCreator.class);
    APC_PRIORITY_LIST.add(AspectJAwareAdvisorAutoProxyCreator.class);
    APC_PRIORITY_LIST.add(AnnotationAwareAspectJAutoProxyCreator.class);
}

實際上它們的優先順序就是在APC_PRIORITY_LIST這個集合中的下標,下標越大優先順序越高,所以AnnotationAwareAspectJAutoProxyCreator的優先順序最高,所以AnnotationAwareAspectJAutoProxyCreator會覆蓋InfrastructureAdvisorAutoProxyCreator,那麼這種覆蓋會不會造成問題呢?答案肯定是不會的,因為你用了這麼久了也沒出過問題嘛~那麼再思考一個問題,為什麼不會出現問題呢?這是因為InfrastructureAdvisorAutoProxyCreator只會使用容器內部定義的Advisor,但是AnnotationAwareAspectJAutoProxyCreator會使用所有實現了Advisor介面的通知,也就是說AnnotationAwareAspectJAutoProxyCreator的作用範圍大於InfrastructureAdvisorAutoProxyCreator,因此這種覆蓋是沒有問題的。限於篇幅原因這個問題我不做詳細解答了,有興趣的同學可以看下兩個類的原始碼。

@EnableTransactionManagement除了註冊了一個AutoProxyRegistrar外,還向容器中註冊了一個ProxyTransactionManagementConfiguration

那麼這個ProxyTransactionManagementConfiguration有什麼作用呢?

如果大家對我文章的風格有一些瞭解的話就會知道,分析一個類一般有兩個切入點

  1. 它的繼承關係
  2. 它提供的API

大家自己在閱讀原始碼時也可以參考這種思路,分析一個類的繼承關係可以讓我們瞭解它從抽象到實現的過程,即使不去細看API也能知道它的大體作用。僅僅知道它的大致作用是不夠的,為了更好了解它的細節我們就需要進一步去探索它的具體實現,也就是它提供的API。這算是我看了這麼就原始碼的一點心得,正好想到了所以在這裡分享下,之後會專門寫一篇原始碼心得的文章

ProxyTransactionManagementConfiguration分析

繼承關係

ProxyTransactionManagementConfiguration

這個類的繼承關係還是很簡單的,只有一個父類AbstractTransactionManagementConfiguration

AbstractTransactionManagementConfiguration

原始碼如下:

@Configuration
public abstract class AbstractTransactionManagementConfiguration implements ImportAware {

	@Nullable
	protected AnnotationAttributes enableTx;

	@Nullable
	protected TransactionManager txManager;

	// 這個方法就是獲取@EnableTransactionManagement的屬性
    // importMetadata:就是@EnableTransactionManagement這個註解所在類的元資訊
	@Override
	public void setImportMetadata(AnnotationMetadata importMetadata) {
        // 將EnableTransactionManagement註解中的屬性對存入到map中
        // AnnotationAttributes實際上就是個map
		this.enableTx = AnnotationAttributes.fromMap(				importMetadata.getAnnotationAttributes(EnableTransactionManagement.class.getName(), false));
        // 這裡可以看到,限定了匯入的註解必須使用@EnableTransactionManagement
        if (this.enableTx == null) {
            throw new IllegalArgumentException(
                "@EnableTransactionManagement is not present on importing class " + importMetadata.getClassName());
        }
	}
	
    // 我們可以配置TransactionManagementConfigurer
    // 通過TransactionManagementConfigurer向容器中註冊一個事務管理器
    // 一般不會這麼使用,更多的是通過@Bean的方式直接註冊
	@Autowired(required = false)
	void setConfigurers(Collection<TransactionManagementConfigurer> configurers) {
		// .....
		TransactionManagementConfigurer configurer = configurers.iterator().next();
		this.txManager = configurer.annotationDrivenTransactionManager();
	}
	
    // 向容器中註冊一個TransactionalEventListenerFactory
    // 這個類用於處理@TransactionalEventListener註解
    // 可以實現對事件的監聽,並且在事務的特定階段對事件進行處理
	@Bean(name = TransactionManagementConfigUtils.TRANSACTIONAL_EVENT_LISTENER_FACTORY_BEAN_NAME)
	@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
	public static TransactionalEventListenerFactory transactionalEventListenerFactory() {
		return new TransactionalEventListenerFactory();
	}

}

TransactionalEventListenerFactory

上面的程式碼中大家可能比較不熟悉的就是TransactionalEventListenerFactory,這個類主要是用來處理@TransactionalEventListener註解的,我們來看一個實際使用的例子

@Component
public class DmzListener {
    // 新增一個監聽器
    // phase = TransactionPhase.AFTER_COMMIT意味著這個方法在事務提交後執行
	@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
	public void listen(DmzTransactionEvent transactionEvent){
		System.out.println("事務已提交");
	}
}

// 定義一個事件
public class DmzTransactionEvent extends ApplicationEvent {
	public DmzTransactionEvent(Object source) {
		super(source);
	}
}


@Component
public class DmzService {
    
	@Autowired
	ApplicationContext applicationContext;
	
    // 一個需要進行事務管理的方法
	@Transactional
	public void invokeWithTransaction() {
        // 釋出一事件
		applicationContext.publishEvent(new DmzTransactionEvent(this));
        // 以一條sout語句提代sql執行過程
		System.out.println("sql invoked");
	}
}

// 測試方法
public class Main {
	public static void main(String[] args) {
		AnnotationConfigApplicationContext ac =
				new AnnotationConfigApplicationContext(Config.class);
		DmzService dmzService = ac.getBean(DmzService.class);
		dmzService.invokeWithTransaction();
	}
}
// 最後程式會按順序輸出
// sql invoked
// 事務已提交

通過上面的例子我們可以看到,雖然我們在invokeWithTransaction方法中一開始就釋出了一個事件,但是監聽事件的方法卻是在invokeWithTransaction才執行的,正常事件的監聽是同步的,假設我們將上述例子中的@TransactionalEventListener註解替換成為@EventListener註解,如下:

@Component
public class DmzListener {
    // 新增一個監聽器
	@EventListener
	public void listen(DmzTransactionEvent transactionEvent){
		System.out.println("事務已提交");
	}
}

這個時候程式的輸出就會是

// 事務已提交
// sql invoked

那麼@TransactionalEventListener註解是實現這種看似非同步(實際上並不是)的監聽方式的呢?

TransactionalEventListenerFactory呼叫鏈

大家按照上面這個呼叫鏈可以找到這麼一段程式碼

image-20200810180616284

通過上面的程式碼,我們可以發現最終會呼叫到TransactionalEventListenerFactorycreateApplicationListener方法,通過這個方法建立一個事件監聽器然後新增到容器中,createApplicationListener方法原始碼如下:

public ApplicationListener<?> createApplicationListener(String beanName, Class<?> type, Method method) {
    return new ApplicationListenerMethodTransactionalAdapter(beanName, type, method);
}

就是建立了一個ApplicationListenerMethodTransactionalAdapter,這個類本身就是一個事件監聽器(實現了ApplicationListener介面)。我們直接關注它的事件監聽方法,也就是onApplicationEvent方法,其原始碼如下:

@Override
public void onApplicationEvent(ApplicationEvent event) {
    // 啟用了同步,並且真實存在事務
    if (TransactionSynchronizationManager.isSynchronizationActive() &&
        TransactionSynchronizationManager.isActualTransactionActive()) {
        // 實際上是依賴事務的同步機制實現的事件監聽
        TransactionSynchronization transactionSynchronization = createTransactionSynchronization(event);
        TransactionSynchronizationManager.registerSynchronization(transactionSynchronization);
    }
    // 在沒有開啟事務的情況下是否處理事件
    else if (this.annotation.fallbackExecution()) {
        // ....
        // 如果註解中的fallbackExecution為true,意味著沒有事務開啟的話
        // 也會執行監聽邏輯
        processEvent(event);
    }
    else {
       // ....
    }
}

到這一步邏輯已經清楚了,@TransactionalEventListener所標註的方法在容器啟動時被解析成了一個ApplicationListenerMethodTransactionalAdapter,這個類本身就是一個事件監聽器,當容器中的元件釋出了一個事件後,如果事件匹配,會進入它的onApplicationEvent方法,這個方法並沒有直接執行我們所定義的監聽邏輯,而是給當前事務註冊了一個同步的行為,當事務到達某一個階段時,這個行為會被觸發。通過這種方式,實現一種偽非同步。實際上註冊到事務的的同步就是TransactionSynchronizationEventAdapter,這個類的原始碼非常簡單,這裡就單獨取它一個方法看下

// 這個方法會在事務提交前執行
public void beforeCommit(boolean readOnly) {
    // 在執行時會先判斷在@TransactionalEventListener註解中定義的phase是不是BEFORE_COMMIT
    // 如果不是的話,什麼事情都不做
   if (this.phase == TransactionPhase.BEFORE_COMMIT) {
      processEvent();
   }
}

別看上面這麼多內容,到目前為止我們還是隻對ProxyTransactionManagementConfiguration的父類做了介紹,接下來我們就來看看ProxyTransactionManagementConfiguration自身做了什麼事情。

原始碼分析

// proxyBeanMethods=false,意味著不對配置類生成代理物件
@Configuration(proxyBeanMethods = false)
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public class ProxyTransactionManagementConfiguration extends AbstractTransactionManagementConfiguration {
	
    // 註冊了一個BeanFactoryTransactionAttributeSourceAdvisor
    // advisor就是一個繫結了切點的通知
    // 可以看到通知就是TransactionInterceptor
    // 切點會通過TransactionAttributeSource去解析@Transacational註解
    // 只會對有這個註解的方法進行攔截
	@Bean(name = TransactionManagementConfigUtils.TRANSACTION_ADVISOR_BEAN_NAME)
    // BeanDefinition的角色是一個基礎設施類
	@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
	public BeanFactoryTransactionAttributeSourceAdvisor transactionAdvisor(
			TransactionAttributeSource transactionAttributeSource, TransactionInterceptor transactionInterceptor) {
		
		BeanFactoryTransactionAttributeSourceAdvisor advisor = new BeanFactoryTransactionAttributeSourceAdvisor();
		advisor.setTransactionAttributeSource(transactionAttributeSource);
		advisor.setAdvice(transactionInterceptor);
		if (this.enableTx != null) {
			advisor.setOrder(this.enableTx.<Integer>getNumber("order"));
		}
		return advisor;
	}
	
    // 註冊一個AnnotationTransactionAttributeSource
    // 這個類的主要作用是用來解析@Transacational註解
	@Bean
	@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
	public TransactionAttributeSource transactionAttributeSource() {
		return new AnnotationTransactionAttributeSource();
	}
	
    // 事務是通過AOP實現的,AOP的核心就是攔截器
    // 這裡就是註冊了實現事務需要的攔截器
	@Bean
	@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
	public TransactionInterceptor transactionInterceptor(TransactionAttributeSource transactionAttributeSource) {
		TransactionInterceptor interceptor = new TransactionInterceptor();
		interceptor.setTransactionAttributeSource(transactionAttributeSource);
		if (this.txManager != null) {
			interceptor.setTransactionManager(this.txManager);
		}
		return interceptor;
	}

}

實現事務管理的核心就是SpringAOP,而完成SpringAOP的核心就是通知(Advice),通知的核心就是攔截器,關於SpringAOP、切點、通知在之前的文章中已經做過詳細介紹了,所以對這一塊本文就跳過了,我們直接定位到事務管理的核心TransactionInterceptor

TransactionInterceptor分析

TransactionInterceptor實現了MethodInterceptor,核心方法就是invoke方法,我們直接定位到這個方法的具體實現邏輯

// invocation:代表了要進行事務管理的方法
public Object invoke(MethodInvocation invocation) throws Throwable {
    Class<?> targetClass = (invocation.getThis() != null ? AopUtils.getTargetClass(invocation.getThis()) : null);
    // 核心方法就是invokeWithinTransaction
    return invokeWithinTransaction(invocation.getMethod(), targetClass, invocation::proceed);
}

invokeWithinTransaction方法分析

這個方法很長,但是主要可以分為三段

  1. 響應式事務管理
  2. 標準事務管理
  3. 通過回撥實現事務管理

這裡我們只分析標準事務管理,下面的原始碼也只保留標準事務管理相關程式碼

protected Object invokeWithinTransaction(Method method, @Nullable Class<?> targetClass,
                                         final InvocationCallback invocation) throws Throwable {
	
    // 之前在配置類中註冊了一個AnnotationTransactionAttributeSource
    // 這裡就是直接返回了之前註冊的那個Bean,通過它去獲取事務屬性
    TransactionAttributeSource tas = getTransactionAttributeSource();
   
	// 解析@Transactional註解獲取事務屬性
    final TransactionAttribute txAttr = (tas != null ? tas.getTransactionAttribute(method, targetClass) : null);

    // 獲取對應的事務管理器
    final TransactionManager tm = determineTransactionManager(txAttr);

    // ...
    // 忽略響應式的事務管理
    // ... 
    
    // 做了個強轉PlatformTransactionManager
    PlatformTransactionManager ptm = asPlatformTransactionManager(tm);

    // 切點名稱(類名+方法名),會被作為事務的名稱
    final String joinpointIdentification = methodIdentification(method, targetClass, txAttr);

    if (txAttr == null || !(ptm instanceof CallbackPreferringPlatformTransactionManager)) {		  // 建立事務
        TransactionInfo txInfo = createTransactionIfNecessary(ptm, txAttr, joinpointIdentification);

        Object retVal;
        try {
            // 這裡執行真正的業務邏輯
            retVal = invocation.proceedWithInvocation();
        }
        catch (Throwable ex) {
         	// 方法執行出現異常,在異常情況下完成事務
            completeTransactionAfterThrowing(txInfo, ex);
            throw ex;
        }
        finally {
            // 清除執行緒中的事務資訊
            cleanupTransactionInfo(txInfo);
        }
        
        // ...
        // 省略不重要程式碼
        // ...
		
        // 提交事務
        commitTransactionAfterReturning(txInfo);
        return retVal;
    }
	// ....
    // 省略回撥實現事務管理相關程式碼
    // ....
    return result;
    }
}

通過上面這段程式碼可以歸納出事務管理的流程如下:

  1. 獲取事務屬性------->tas.getTransactionAttribute
  2. 建立事務------------->createTransactionIfNecessary
  3. 執行業務邏輯------->invocation.proceedWithInvocation
  4. 異常時完成事務---->completeTransactionAfterThrowing
  5. 清除執行緒中繫結的事務資訊----->cleanupTransactionInfo
  6. 提交事務------------->commitTransactionAfterReturning

接下來我們一步步分析

1、獲取事務屬性

// 獲取事務對應的屬性,實際上返回一個AnnotationTransactionAttributeSource
// 之後再呼叫AnnotationTransactionAttributeSource的getTransactionAttribute
// getTransactionAttribute:先從攔截的方法上找@Transactional註解
// 如果方法上沒有的話,再從方法所在的類上找,如果類上還沒有的話嘗試從介面或者父類上找
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);
        // 快取解析的結果,如果為事務屬性為null,也放入一個標誌
        // 代表這個方法不需要進行事務管理
        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);
            }
            this.attributeCache.put(cacheKey, txAttr);
        }
        return txAttr;
    }
}

真正解析註解時呼叫了computeTransactionAttribute方法,其程式碼如下:

protected TransactionAttribute computeTransactionAttribute(Method method, @Nullable Class<?> targetClass) {
    // 預設情況下allowPublicMethodsOnly為true
    // 這意味著@Transactional如果放在非public方法上不會生效
    if (allowPublicMethodsOnly() && !Modifier.isPublic(method.getModifiers())) {
        return null;
    }

    // method是介面中的方法
    // specificMethod是具體實現類的方法
    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;
}

可以看到在computeTransactionAttribute方法中又進一步呼叫了findTransactionAttribute方法,我們一步步跟蹤最終會進入到SpringTransactionAnnotationParser#parseTransactionAnnotation(AnnotationAttributes)這個方法中

整個呼叫鏈我這裡也畫出來了,感興趣的大家可以跟一下

事務註解解析呼叫棧

對於本文,我們直接定位到最後一步,對應原始碼如下:

protected TransactionAttribute parseTransactionAnnotation(AnnotationAttributes attributes) {
    // 最終返回的是一個RuleBasedTransactionAttribute
    // 在上篇文章分析過了,定義了在出現異常時如何回滾
    RuleBasedTransactionAttribute rbta = new RuleBasedTransactionAttribute();
    Propagation propagation = attributes.getEnum("propagation");
    rbta.setPropagationBehavior(propagation.value());
    Isolation isolation = attributes.getEnum("isolation");
    rbta.setIsolationLevel(isolation.value());
    rbta.setTimeout(attributes.getNumber("timeout").intValue());
    rbta.setReadOnly(attributes.getBoolean("readOnly"));
    rbta.setQualifier(attributes.getString("value"));

    List<RollbackRuleAttribute> rollbackRules = new ArrayList<>();
    for (Class<?> rbRule : attributes.getClassArray("rollbackFor")) {
        rollbackRules.add(new RollbackRuleAttribute(rbRule));
    }
    for (String rbRule : attributes.getStringArray("rollbackForClassName")) {
        rollbackRules.add(new RollbackRuleAttribute(rbRule));
    }
    for (Class<?> rbRule : attributes.getClassArray("noRollbackFor")) {
        rollbackRules.add(new NoRollbackRuleAttribute(rbRule));
    }
    for (String rbRule : attributes.getStringArray("noRollbackForClassName")) {
        rollbackRules.add(new NoRollbackRuleAttribute(rbRule));
    }
    rbta.setRollbackRules(rollbackRules);
    return rbta;
}

到這一步很明確了,解析了@Transactional註解,並將這個註解的屬性封裝到了一個RuleBasedTransactionAttribute物件中返回。

2、 建立事務

對應程式碼如下:

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

    // 如果沒有為事務指定名稱,使用切點作為事務名稱
    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);
        }
        // ....省略日誌
    }
    // 將事務相關資訊封裝到TransactionInfo物件中
    // 並將TransactionInfo繫結到當前執行緒
    return prepareTransactionInfo(tm, txAttr, joinpointIdentification, status);
}

從上面方法簽名上我們可以看到,建立事務實際上就是建立了一個TransactionInfo。一個TransactionInfo物件包含了事務相關的所有資訊,例如實現事務使用的事務管理器(PlatformTransactionManager),事務的屬性(TransactionAttribute),事務的狀態(transactionStatus)以及與當前建立的關聯的上一個事務資訊(oldTransactionInfo)。我們可以通過TransactionInfo物件的hasTransaction方法判斷是否真正建立了一個事務。

上面的核心程式碼只有兩句

  1. tm.getTransaction,通過事務管理器建立事務
  2. prepareTransactionInfo,封裝TransactionInfo並繫結到執行緒上

我們先來看看getTransaction幹了啥,對應程式碼如下:

這個程式碼應該是整個Spring實現事務管理裡面最難的了,因為牽涉到事務的傳播機制,不同傳播級別是如何進行處理的就是下面這段程式碼決定的,比較難,希望大家能耐心看完

public final TransactionStatus getTransaction(@Nullable TransactionDefinition definition)
    throws TransactionException {

    // 事務的屬性(TransactionAttribute),通過解析@Transacational註解de'dao
    TransactionDefinition def = (definition != null ? definition : TransactionDefinition.withDefaults());

    // 獲取一個資料庫事務物件(DataSourceTransactionObject),
    // 這個物件中封裝了一個從當前執行緒上下文中獲取到的連線 
    Object transaction = doGetTransaction();
    boolean debugEnabled = logger.isDebugEnabled();

    // 判斷是否存在事務
    // 如果之前獲取到的連線不為空,並且連線上啟用了事務,那麼就為true
    if (isExistingTransaction(transaction)) {
        // 如果已經存在了事務,需要根據不同傳播機制進行不同的處理
        return handleExistingTransaction(def, transaction, debugEnabled);
    }

    // 校驗事務的超時設定,預設為-1,代表不進行超時檢查
    if (def.getTimeout() < TransactionDefinition.TIMEOUT_DEFAULT) {
        throw new InvalidTimeoutException("Invalid transaction timeout", def.getTimeout());
    }

    // 檢查隔離級別是否為mandatory(強制性要求必須開啟事務)
    // 如果為mandatory,但是沒有事務存在,那麼丟擲異常
    if (def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_MANDATORY) {
        throw new IllegalTransactionStateException(
            "No existing transaction found for transaction marked with propagation 'mandatory'");
    }
    // 目前為止沒有事務,並且隔離級別不是mandatory
    else if (def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRED ||
             def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRES_NEW ||
             def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NESTED) {
        // 當隔離級別為required,required_new,nested時均需要新建事務

        // 如果存在同步,將註冊的同步掛起
        SuspendedResourcesHolder suspendedResources = suspend(null);
        if (debugEnabled) {
            logger.debug("Creating new transaction with name [" + def.getName() + "]: " + def);
        }
        try {
            // 開啟一個新事務
            return startTransaction(def, transaction, debugEnabled, suspendedResources);
        }
        catch (RuntimeException | Error ex) {
            resume(null, suspendedResources);
            throw ex;
        }
    }
    else {
        // 建立一個空事務,沒有實際的事務提交以及回滾機制
        // 會啟用同步:將資料庫連線繫結到當前執行緒上

        boolean newSynchronization = (getTransactionSynchronization() == SYNCHRONIZATION_ALWAYS);
        return prepareTransactionStatus(def, null, true, newSynchronization, debugEnabled, null);
    }
}

上面這段程式碼可以分為兩種情況分析

  1. 應用程式直接呼叫了一個被事務管理的方法(直接呼叫)

  2. 在一個需要事務管理的方法中呼叫了另外一個需要事務管理的方法(巢狀呼叫)

用程式碼表示如下:

@Service
public class IndexService {
	@Autowired
	DmzService dmzService;

	// 直接呼叫
	@Transactional
	public void directTransaction(){
		// ......
	}

	// 巢狀呼叫
	@Transactional
	public void nestedTransaction(){
		dmzService.directTransaction();
	}
}

ps:我們暫且不考慮自呼叫的情況,因為自呼叫可能會出現事務失效,在下篇文章我們專門來聊一聊事務管理中的那些坑

直接呼叫

我們先來看看直接呼叫的情況下上述程式碼時如何執行的

直接呼叫事務方法流程

  1. doGetTransaction原始碼分析
protected Object doGetTransaction() {
    DataSourceTransactionObject txObject = new DataSourceTransactionObject();

    // 在建立DataSourceTransactionManager將其設定為了true
    // 標誌是否允許
    txObject.setSavepointAllowed(isNestedTransactionAllowed());

    // 從執行緒上下文中獲取到對應的這個連線池中的連線
    // 獲取對應資料來源下的這個繫結的連線
    // 當我們將資料庫連線繫結到執行緒上時,實際上繫結到當前執行緒的是一個map
    // 其中key是對應的資料來源,value是通過這個資料來源獲取的一個連線
    ConnectionHolder conHolder =(ConnectionHolder)TransactionSynchronizationManager
        .getResource(obtainDataSource());
    // 如果當前上下文中已經有這個連線了,那麼將newConnectionHolder這個標誌設定為false
    // 代表複用了之前的連線(不是一個新連線)
    txObject.setConnectionHolder(conHolder, false);
    return txObject;
}

直接呼叫的情況下,獲取到的連線肯定為空,所以這裡返回的是一個沒有持有資料庫連線的DataSourceTransactionObject

  1. isExistingTransaction原始碼分析
protected boolean isExistingTransaction(Object transaction) {
    DataSourceTransactionObject txObject = (DataSourceTransactionObject) transaction;
    // 只有在存在連線並且連線上已經啟用了事務才會返回true
    return (txObject.hasConnectionHolder() && txObject.getConnectionHolder().isTransactionActive());
}

直接呼叫的情況下,這裡肯定是返回fasle

  1. 從這裡可以看出,如果直接呼叫了一個傳播級別為MANDATORY的方法將會丟擲異常
  2. 傳播級別為required、requires_new、nested時,會真正開啟事務,對應程式碼如下
else if (def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRED ||
         def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRES_NEW ||
         def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NESTED) {
    
    // 如果存在同步,將註冊的同步掛起
    SuspendedResourcesHolder suspendedResources = suspend(null);
    if (debugEnabled) {
        logger.debug("Creating new transaction with name [" + def.getName() + "]: " + def);
    }
    try {
        // 開啟一個新事務
        return startTransaction(def, transaction, debugEnabled, suspendedResources);
    }
    catch (RuntimeException | Error ex) {
        resume(null, suspendedResources);
        throw ex;
    }
}

我們先來看看它的掛起操作幹了什麼,對應程式碼如下:

// 直接呼叫時,傳入的transaction為null
protected final SuspendedResourcesHolder suspend(@Nullable Object transaction) throws TransactionException {
    
    // 在之前的程式碼中沒有進行任何啟用同步的操作,所以不會進入下面這個判斷
    if (TransactionSynchronizationManager.isSynchronizationActive()) {
       // ...
    }
   
    // 傳入的transaction為null,這個判斷也不進
    else if (transaction != null) {
       // ...
    }
    else {
        return null;
    }
}

從上面可以看出,這段程式碼其實啥都沒幹,OK,減負,直接跳過。

接著,就是真正的開啟事務了,會呼叫一個startTransaction方法,對應程式碼如下:

private TransactionStatus startTransaction(TransactionDefinition definition, Object transaction,
                                           boolean debugEnabled, @Nullable SuspendedResourcesHolder suspendedResources) {
	// 預設情況下,getTransactionSynchronization方法會返回SYNCHRONIZATION_ALWAYS
    // 所以這裡是true
    boolean newSynchronization = (getTransactionSynchronization() != SYNCHRONIZATION_NEVER);
    
    // 根據之前的事務定義等相關資訊構造一個事務狀態物件
    DefaultTransactionStatus status = newTransactionStatus(
        definition, transaction, true, newSynchronization, debugEnabled, suspendedResources);
    
    // 真正開啟事務,會從資料來源中獲取連線並繫結到執行緒上
    doBegin(transaction, definition);
    
    // 在這裡會啟用同步
    prepareSynchronization(status, definition);
    return status;
}

newTransactionStatus實際上就是呼叫了DefaultTransactionStatus的建構函式,我們來看一看每個引數的含義以及實際傳入的是什麼。對應程式碼如下:

// definition:事務的定義,解析@Transactional註解得到的
// transaction:通過前面的doGetTransaction方法得到的,關聯了一個資料庫連線
// newTransaction:是否是一個新的事務
// debug:是否開啟debug級別日誌
// newSynchronization:是否需要一個新的同步
// suspendedResources:代表了執行當前事務時掛起的資源
protected DefaultTransactionStatus newTransactionStatus(
    TransactionDefinition definition, @Nullable Object transaction, boolean newTransaction,
    boolean newSynchronization, boolean debug, @Nullable Object suspendedResources) {
	// 是否是一個真實的新的同步
    // 除了傳入的標誌之外還需要判斷當前執行緒上的同步是否啟用
    // 沒有啟用才算是一個真正的新的同步
    boolean actualNewSynchronization = newSynchronization &&
        !TransactionSynchronizationManager.isSynchronizationActive();
    
    // 返回一個事務狀態物件
    // 包含了事務的定於、事務使用的連線、事務是否要開啟一個新的同步、事務掛起的資源等
    return new DefaultTransactionStatus(
        transaction, newTransaction, actualNewSynchronization,
        definition.isReadOnly(), debug, suspendedResources);
}

在完成事務狀態物件的構造之後,就是真正的開啟事務了,我們也不難猜出所謂開啟事務其實就是從資料來源中獲取一個一個連線並設定autoCommit為false。對應程式碼如下:

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

    try {
        // 判斷txObject中是否存在連線並且連線上已經啟用了事務
        // txObject是通過之前的doGetTransaction方法得到的
        // 直接呼叫的情況下,這個判斷肯定為true
        if (!txObject.hasConnectionHolder() ||
            txObject.getConnectionHolder().isSynchronizedWithTransaction()) {
            // 從資料來源中獲取一個連線
            Connection newCon = obtainDataSource().getConnection();
            }
        	// 將連線放入到txObject中
        	// 第二個引數為true,標誌這是一個新連線
            txObject.setConnectionHolder(new ConnectionHolder(newCon), true);
        }
		// 標誌連線資源跟事務同步
        txObject.getConnectionHolder().setSynchronizedWithTransaction(true);
        con = txObject.getConnectionHolder().getConnection();
		
    	// 應用事務定義中的read only跟隔離級別
    	// 實際就是呼叫了Connection的setReadOnly跟setTransactionIsolation方法
    	// 如果事務定義中的隔離級別跟資料庫預設的隔離級別不一致會返回的是資料庫預設的隔離級別 
    	// 否則返回null
    	// 主要是為了在事務完成後能將連線狀態恢復
        Integer previousIsolationLevel = DataSourceUtils.prepareConnectionForTransaction(con, definition);
        txObject.setPreviousIsolationLevel(previousIsolationLevel);
        txObject.setReadOnly(definition.isReadOnly());
		
    	// 設定autoCommit為false,顯示開啟事務
        if (con.getAutoCommit()) {
            // 代表在事務完成後需要將連線重置為autoCommit=true
            // 跟之前的previousIsolationLevel作用一樣,都是為了恢復連線
            txObject.setMustRestoreAutoCommit(true);
            con.setAutoCommit(false);
        }
		
    	// 是否通過顯示的語句設定read only,預設是不需要的
        prepareTransactionalConnection(con, definition);
        txObject.getConnectionHolder().setTransactionActive(true);
		
    	// 設定超時時間
        int timeout = determineTimeout(definition);
        if (timeout != TransactionDefinition.TIMEOUT_DEFAULT) {
            txObject.getConnectionHolder().setTimeoutInSeconds(timeout);
        }

        // 將連線繫結到當前執行緒上下文中,實際存入到執行緒上下文的是一個map
    	// 其中key為資料來源,value為從該資料來源中獲取的一個連線
        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);
    }
}

歸納起來,doBegin做了這麼幾件事

  1. 從資料來源中獲取一個連線
  2. 將事務定義應用到這個連線上
  3. 通過這個連線物件顯示開啟事務
  4. 將這個連線繫結到執行緒上下文

在通過doBegin開啟了事務後,接下來呼叫了prepareSynchronization,這個方法的主要目的就是為了準備這個事務需要的同步,對應原始碼如下:

protected void prepareSynchronization(DefaultTransactionStatus status, TransactionDefinition definition) {
    if (status.isNewSynchronization()) {
        // 到這裡真正啟用了事務
 TransactionSynchronizationManager.setActualTransactionActive(status.hasTransaction());
        // 隔離級別
        // 只有在不是預設隔離級別的情況下才會繫結到執行緒上,否則繫結一個null
        TransactionSynchronizationManager.setCurrentTransactionIsolationLevel(
            definition.getIsolationLevel() != TransactionDefinition.ISOLATION_DEFAULT ?
            definition.getIsolationLevel() : null);
        // 是否只讀
       TransactionSynchronizationManager.setCurrentTransactionReadOnly(definition.isReadOnly());
       TransactionSynchronizationManager.setCurrentTransactionName(definition.getName());
        // 初始化同步行為
        TransactionSynchronizationManager.initSynchronization();
    }
}

主要對一些資訊進行了同步,例如事務真正啟用了事務,事務隔離級別,事務名稱,是否只讀。同時初始化了同步事務過程中要執行的一些回撥(也就是一些同步的行為)

在前面我們已經介紹了在直接呼叫的情況下,如果傳播級別為mandatory會直接丟擲異常,傳播級別為required、requires_new、nested時,會呼叫startTransaction真正開啟一個事務,但是除了這幾種傳播級別之外還有supports、not_supported、never。這幾種傳播級別在直接呼叫時會做什麼呢?前面那張圖我其實已經畫出來了,會開啟一個空事務("empty" transaction),對應程式碼如下:

public final TransactionStatus getTransaction(@Nullable TransactionDefinition definition)
    throws TransactionException {
    
    // ......
    // mandatory丟擲異常
    // required、requires_new、nested呼叫startTransaction開啟事務
    // supports、not_supported、never會進入下面這個判斷
    else {
        // 預設同步級別就是always
        boolean newSynchronization = (getTransactionSynchronization() == SYNCHRONIZATION_ALWAYS);
        // 直接呼叫prepareTransactionStatus返回一個事務狀態物件
        return prepareTransactionStatus(def, null, true, newSynchronization, debugEnabled, null);
    }
}

prepareTransactionStatus程式碼如下

// 傳入的引數為:def, null, true, newSynchronization, debugEnabled, null
protected final DefaultTransactionStatus prepareTransactionStatus(
    TransactionDefinition definition, @Nullable Object transaction, boolean newTransaction,
    boolean newSynchronization, boolean debug, @Nullable Object suspendedResources) {
   	// 呼叫這個方法時
    // definition:傳入的是解析@Transactional註解得到的事務屬性
    // transaction:寫死的傳入為null,意味著沒有真正的事務()
    // newTransaction:寫死的傳入為true
    // newSynchronization:預設同步級別為always,在沒有真正事務的時候也進行同步
    // suspendedResources:寫死了,傳入為null,不掛起任何資源
    DefaultTransactionStatus status = newTransactionStatus(
        definition, transaction, newTransaction, newSynchronization, debug, suspendedResources);
    prepareSynchronization(status, definition);
    return status;
}

可以看到這個方法跟之前介紹的startTransaction方法相比較下就有幾點不同,最明顯的是少了一個步驟,在prepareTransactionStatus方法中沒有呼叫doBegin方法,這意味這個這個方法不會去獲取資料庫連線,更不會繫結資料庫連線到上下文中,僅僅是做了一個同步的初始化。

其次,startTransaction方法在呼叫newTransactionStatus傳入的第二個引數是從doGetTransaction方法中獲取的,不可能為null,而呼叫prepareTransactionStatus方法時,寫死的傳入為null。這也代表了prepareTransactionStatus不會真正開啟一個事務。

雖然不會真正開啟一個事務,只是開啟了一個“空事務”,但是當這個空事務完成時仍然會觸發註冊的回撥。

巢狀呼叫

巢狀呼叫流程

前面已經介紹了在直接呼叫下七種不同隔離級別在建立事務時的不同表現,程式碼看似很多,實際還是比較簡單的,接下來我們要介紹的就是巢狀呼叫,也就是已經存在事務的情況下,呼叫了另外一個被事務管理的方法(並且事務管理是生效的)。我們需要關注的是下面這段程式碼

public final TransactionStatus getTransaction(@Nullable TransactionDefinition definition)
    throws TransactionException {

    // 事務的屬性(TransactionAttribute)
    TransactionDefinition def = (definition != null ? definition : TransactionDefinition.withDefaults());

    // 從執行緒上下文中獲取到一個連線,並封裝到一個DataSourceTransactionObject物件中
    Object transaction = doGetTransaction();
    boolean debugEnabled = logger.isDebugEnabled();

    // 判斷之前獲取到的事務上是否有連線,並且連線上啟用了事務
    if (isExistingTransaction(transaction)) 
        // 巢狀呼叫處理在這裡
        return handleExistingTransaction(def, transaction, debugEnabled);
    }
    // ...
    // 下面是直接呼叫的情況,前文已經分析過了
    // 省略直接呼叫的相關程式碼
}

處理巢狀呼叫的核心程式碼其實就是handleExistingTransaction。但是進入這個方法前首先isExistingTransaction這個方法得返回true才行,所以我們先來看看isExistingTransaction這個方法做了什麼,程式碼如下:

protected boolean isExistingTransaction(Object transaction) {
    DataSourceTransactionObject txObject = (DataSourceTransactionObject) transaction;
	// 首先判斷txObject中是否已經繫結了連線
    // 其次判斷這個連線上是否已經啟用了事務
    return (txObject.hasConnectionHolder() && txObject.getConnectionHolder().isTransactionActive());
}

結合我們之前分析的直接呼叫的程式碼邏輯,可以看出,只有外圍的事務的傳播級別為required、requires_new、nested時,這個判斷才會成立。因為只有在這些傳播級別下才會真正的開啟事務,才會將連線繫結到當前執行緒,doGetTransaction方法才能返回一個已經繫結了資料庫連線的事務物件。

在滿足了isExistingTransaction時,會進入巢狀呼叫的處理邏輯,也就是handleExistingTransaction方法,其程式碼如下:

程式碼很長,但是大家跟著我一步步梳理就可以了,其實邏輯也不算特別複雜

整個程式碼邏輯其實就是為了實現事務的傳播,在不同的傳播級別下做不同的事情

private TransactionStatus handleExistingTransaction(
      TransactionDefinition definition, Object transaction, boolean debugEnabled)
      throws TransactionException {
	
   // 如果巢狀的事務的傳播級別為never,那麼直接丟擲異常
   if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NEVER) {
      throw new IllegalTransactionStateException(
            "Existing transaction found for transaction marked with propagation 'never'");
   }
   
   // 如果巢狀的事務的傳播級別為not_soupported,那麼掛起外圍事務
   if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NOT_SUPPORTED) {
      if (debugEnabled) {
         logger.debug("Suspending current transaction");
      }
      // 掛起外圍事務
      Object suspendedResources = suspend(transaction);
      boolean newSynchronization = (getTransactionSynchronization() == SYNCHRONIZATION_ALWAYS);
      // 開啟一個新的空事務
      return prepareTransactionStatus(
            definition, null, false, newSynchronization, debugEnabled, suspendedResources);
   }

    // 如果巢狀的事務傳播級別為requires_new,那麼掛起外圍事務,並且新建一個新的事務
   if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRES_NEW) {
     
      SuspendedResourcesHolder suspendedResources = suspend(transaction);
      try {
         return startTransaction(definition, transaction, debugEnabled, suspendedResources);
      }
      catch (RuntimeException | Error beginEx) {
         resumeAfterBeginException(transaction, suspendedResources, beginEx);
         throw beginEx;
      }
   }
   
   // 如果巢狀事務的傳播級別為nested,會獲取當前執行緒繫結的資料庫連線
   // 並通過資料庫連線建立一個儲存點(save point)
   // 其實就是呼叫Connection的setSavepoint方法
   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'");
      }
	  // 預設是true
      if (useSavepointForNestedTransaction()) {
         DefaultTransactionStatus status =
               prepareTransactionStatus(definition, transaction, false, false, debugEnabled, null);
         status.createAndHoldSavepoint();
         return status;
      }
      else {
         // JTA進行事務管理才會進入這這裡,我們不做考慮
         return startTransaction(definition, transaction, debugEnabled, null);
      }
   }

   if (debugEnabled) {
      logger.debug("Participating in existing transaction");
   }
   // 巢狀事務傳播級別為supports、required、mandatory時,是否需要校驗巢狀事務的屬性
   // 主要校驗的是個隔離級別跟只讀屬性
   // 預設是不需要校驗的
   // 如果開啟了校驗,那麼會判斷如果外圍事務的隔離級別跟巢狀事務的隔離級別是否一致
   // 如果不一致,直接丟擲異常
   if (isValidateExistingTransaction()) {
      if (definition.getIsolationLevel() != TransactionDefinition.ISOLATION_DEFAULT) {
         Integer currentIsolationLevel = TransactionSynchronizationManager.getCurrentTransactionIsolationLevel();
         if (currentIsolationLevel == null || currentIsolationLevel != definition.getIsolationLevel()) {
            Constants isoConstants = DefaultTransactionDefinition.constants;
            // 這裡會丟擲異常
         }
      }
      // 巢狀事務的只讀為false
      if (!definition.isReadOnly()) {
          // 但是外圍事務的只讀為true,那麼直接丟擲異常
         if (TransactionSynchronizationManager.isCurrentTransactionReadOnly()) {
           // 這裡會丟擲異常
         }
      }
   }
   boolean newSynchronization = (getTransactionSynchronization() != SYNCHRONIZATION_NEVER);
   return prepareTransactionStatus(definition, transaction, false, newSynchronization, debugEnabled, null);
}

觀察上面的程式碼我們可以發現,返回值只有兩個情況(不考慮丟擲異常)

  1. 呼叫了startTransaction方法
  2. 呼叫了prepareTransactionStatus方法

在前面我們已經介紹了這兩個方法的區別,相比較於startTransaction方法,prepareTransactionStatus並不會再去資料來源中獲取連線。

另外我們還可以發現,只有傳播級別為required_new的情況才會去呼叫startTransaction方法(不考慮JTA),也就是說,只有required_new才會真正的獲取一個新的資料庫連線,其餘的情況如果支援事務的話都是複用了外圍事務獲取到的連線,也就是說它們其實是加入了外圍的事務中,例如supports、required、mandatory、nested,其中nested又比較特殊,因為它不僅僅是單純的加入了外圍的事務,而且在加入前設定了一個儲存點,如果僅僅是巢狀事務發生了異常,會回滾到之前設定的這個儲存點上。另外需要注意的是,因為是直接複用了外部事務的連線,所以supports、required、mandatory、nested這幾種傳播級別下,巢狀的事務會隨著外部事務的提交而提交,同時也會跟著外部事物的回滾而回滾。

接下來我們開始細節性的分析上邊的程式碼,對於傳播級別為never,沒啥好說的,直接丟擲異常,因為不支援在事務中執行嘛~

當傳播級別為not_supported時會進入下面這段程式碼

// 如果巢狀的事務的傳播級別為not_soupported,那麼掛起外圍事務
if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NOT_SUPPORTED) {
    if (debugEnabled) {
        logger.debug("Suspending current transaction");
    }
    // 掛起外圍事務
    Object suspendedResources = suspend(transaction);
    boolean newSynchronization = (getTransactionSynchronization() == SYNCHRONIZATION_ALWAYS);
    // 開啟一個新的空事務
    return prepareTransactionStatus(
        definition, null, false, newSynchronization, debugEnabled, suspendedResources);
}

主要就是兩個動作,首先掛起外圍事務。很多同學可能不是很理解掛起這個詞的含義,掛起實際上做了什麼呢?

  1. 清空外圍事務繫結線上程上的同步
  2. 掛起是因為將來還要恢復,所以不能單純的只是清空呀,還得將清空的資訊儲存到當前的事務上,這樣噹噹前的事務完成後可以恢復到掛起時的狀態,以便於外圍的事務能夠正確的執行下去

其中第一步對應的程式碼如下:

protected final SuspendedResourcesHolder suspend(@Nullable Object transaction) throws TransactionException {
    // 巢狀呼叫下,同步是已經被啟用了的
    if (TransactionSynchronizationManager.isSynchronizationActive()) {
        // 解綁執行緒上繫結的同步回撥,並返回
        List<TransactionSynchronization> suspendedSynchronizations = doSuspendSynchronization();
        try {
            Object suspendedResources = null;
            if (transaction != null) {
                // 這裡實際上就是解綁執行緒上繫結的資料庫連線
                // 同時返回這個連線
                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);
            // 最後集中封裝為一個SuspendedResourcesHolder返回
            return new SuspendedResourcesHolder(
                suspendedResources, suspendedSynchronizations, name, readOnly, isolationLevel, wasActive);
        }
        catch (RuntimeException | Error ex) {
            // 出現異常的話,恢復
            doResumeSynchronization(suspendedSynchronizations);
            throw ex;
        }
    }
    else if (transaction != null) {
        // 如果沒有啟用同步,那麼也需要將連線掛起
        Object suspendedResources = doSuspend(transaction);
        return new SuspendedResourcesHolder(suspendedResources);
    }
    else {
        // Neither transaction nor synchronization active.
        return null;
    }
}

在上面的程式碼我們要注意到一個操作,它會清空執行緒繫結的資料庫連線,同時在後續操作中也不會再去獲取一個資料庫連線重新繫結到當前執行緒上,所以not_supported傳播級別下每次執行SQL都可能使用的不是同一個資料庫連線物件(依賴於業務中獲取連線的方式)。這點大家要注意,跟後面的幾種有很大的區別。

獲取到需要掛起的資源後,呼叫了prepareTransactionStatus,這個方法我們之前分析過了,但是在這裡傳入的引數是不同的

// definition:非null,解析事務註解得來的
// transaction:null
// newTransaction:false
// newSynchronization:true
// suspendedResources:代表掛起的資源,包括連線以及同步
protected final DefaultTransactionStatus prepareTransactionStatus(
      TransactionDefinition definition, @Nullable Object transaction, boolean newTransaction,
      boolean newSynchronization, boolean debug, @Nullable Object suspendedResources) {
   DefaultTransactionStatus status = newTransactionStatus(
         definition, transaction, newTransaction, newSynchronization, debug, suspendedResources);
   prepareSynchronization(status, definition);
   return status;
}

最終會返回一個這樣的事務狀態物件,其中的transaction為null、newTransaction為false,這二者代表了不存在一個真正的事務。在後續的事務提交跟回滾時會根據事務狀態物件中的這兩個屬性來判斷是否需要真正執行回滾,如果不存在真正的事務,那麼也就沒有必要去回滾(當然,這只是針對內部的空事務而言,如果丟擲的異常同時中斷了外部事務,那麼外部事務還是會回滾的)。除了這兩個屬性外,還有newSynchronization,因為在掛起同步時已經將之前的同步清空了,所以newSynchronization仍然為true,這個屬性會影響後續的一些同步回撥,只有為true的時候才會執行回撥操作。最後就是suspendedResources,後續需要根據這個屬性來恢復外部事務的狀態。

當傳播級別為requires_new時,會進入下面這段程式碼

if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRES_NEW) {
   // 第一步,也是掛起
   SuspendedResourcesHolder suspendedResources = suspend(transaction);
   try {
       // 第二步,開啟一個新事務,會繫結一個新的連線到當前執行緒上
      return startTransaction(definition, transaction, debugEnabled, suspendedResources);
   }
   catch (RuntimeException | Error beginEx) {
       // 出現異常,恢復外部事務狀態
      resumeAfterBeginException(transaction, suspendedResources, beginEx);
      throw beginEx;
   }
}

很簡單吧,掛起事務,然後新開一個事務,並且新事務的連線跟外圍事務的不一樣。也就是說這兩個事務互不影響。它也會返回一個事務狀態物件,但是不同的是,transaction不為null、newTransaction為true。也就是說它有自己的提交跟回滾機制,也不難理解,畢竟是兩個不同的資料庫連線嘛~

當傳播級別為nested進入下面這段程式碼

if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NESTED) {
   if (useSavepointForNestedTransaction()) {
      DefaultTransactionStatus status =
            prepareTransactionStatus(definition, transaction, false, false, debugEnabled, null);
      status.createAndHoldSavepoint();
      return status;
   }
   else {
       // JTA相關,不考慮
      return startTransaction(definition, transaction, debugEnabled, null);
   }
}

前面其實已經介紹過了,也很簡單。還是把重點放在返回的事務狀態物件中的那幾個關鍵屬性上,transaction不為null,但是newTransaction為false,也就是說它也不是一個新事務,另外需要注意的是,它沒有掛起任何事務相關的資源,僅僅是建立了一個儲存點而已。這個事務在回滾時,只會回滾到指定的儲存點。同時因為它跟外圍事務共用一個連線,所以它會跟隨外圍事務的提交而提交,回滾而回滾。

剩下的supports、required、mandatory這幾種傳播級別都會進入下面這段程式碼

// 省略了校驗相關程式碼,前面已經介紹過了,預設是關閉校驗的
boolean newSynchronization = (getTransactionSynchronization() != SYNCHRONIZATION_NEVER);
return prepareTransactionStatus(definition, transaction, false, newSynchronization, debugEnabled, null);

我們會發現相比較於nested只是少了一個建立儲存點的動作。最終返回的事務狀態物件中的屬性,transaction不為null,但是newTransaction為false,也就是說它也不是一個新事務。同時因為沒有掛起外圍事務的同步,所以它也不是一個新的同步(newSynchronization為false)。

對於每個隔離級別下返回的事務狀態物件中的屬性希望大家有一定了解,因為後續的回滾、提交等操作都依賴於這個事務狀態物件。

到目前為止,我們就介紹完了事務的建立,緊接著就是真正的執行業務程式碼了,要保證業務程式碼能被事務管理,最重要的一點是保證在業務程式碼中執行SQL時仍然是使用我們在開啟事務時繫結到執行緒上的資料庫連線。那麼是如何保證的呢?我們分為兩種情況討論

  1. 使用JdbcTemplate訪問資料庫
  2. 使用Mybatis訪問資料庫

對於第二種,可能需要你對Mybatis稍微有些瞭解

3、執行業務程式碼

JdbcTemplate

我們要討論的只是JdbcTemplate是如何獲取到之前繫結線上程上的連線這個問題,不要想的太複雜

在事務專題的第一篇我就對JdbcTemplate做了一個簡單的原始碼分析,它底層獲取資料庫連線實際上就是呼叫了DataSourceUtils#doGetConnection方法,程式碼如下:

image-20200815232212399

關鍵程式碼我已經標出來了,實際上獲取連線時也是從執行緒上下文中獲取

Mybatis

Mybatis相對來說比較複雜,大家目前做為了解即可。當Spring整合Mybatis時,事務是交由Spring來管理的,那麼Spring是如何接管Mybatis的事務的呢?核心程式碼位於SqlSessionFactoryBean#buildSqlSessionFactory方法中。其中有這麼一段程式碼

image-20200815232625292

在這裡替換掉了Mybatis的事務工廠(Mybatis依賴事務工廠建立的事務物件來獲取連線),使用了Spring自己實現的一個事務工廠SpringManagedTransactionFactory。通過它可以獲取一個事務物件SpringManagedTransaction。我們會發現這個事務物件在獲取連線時呼叫的也是DataSourceUtils的方法

private void openConnection() throws SQLException {
  this.connection = DataSourceUtils.getConnection(this.dataSource);
  this.autoCommit = this.connection.getAutoCommit();
  this.isConnectionTransactional = DataSourceUtils.isConnectionTransactional(this.connection, this.dataSource);
}

所以它也能保證使用的是開啟事務時繫結線上程上的連線,從而保證事務的正確性。

4、執行出現異常

出現異常時其實分為兩種情況,並不是一定會回滾

對應程式碼如下:

protected void completeTransactionAfterThrowing(@Nullable TransactionInfo txInfo, Throwable ex) {
    if (txInfo != null && txInfo.getTransactionStatus() != null) {

        // transactionAttribute是從@Transactional註解中解析得來的
        if (txInfo.transactionAttribute != null && txInfo.transactionAttribute.rollbackOn(ex)) {
            try {
                // 回滾
                txInfo.getTransactionManager().rollback(txInfo.getTransactionStatus());
            }
            // 省略異常處理
        }
        else {
            try {
                // 即使出現異常仍然提交事務
                txInfo.getTransactionManager().commit(txInfo.getTransactionStatus());
            }
            // 省略異常處理
        }
    }
}

可以看到,只有在滿足txInfo.transactionAttribute.rollbackOn(ex)這個條件時才會真正執行回滾,否則即使出現了異常也會先提交事務。這個條件取決於@Transactiona註解中的rollbackFor屬性是如何配置的,如果不進行配置的話,預設只會對RuntimeException或者Error進行回滾。

在進行回滾時會呼叫事務管理器的rollback方法,對應程式碼如下:

public final void rollback(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;
    // 這裡真正處理回滾
    processRollback(defStatus, false);
}
private void processRollback(DefaultTransactionStatus status, boolean unexpected) {
   try {
       // 傳入時寫死的為false
      boolean unexpectedRollback = unexpected;

      try {
         // 觸發之前註冊的同步回撥
         triggerBeforeCompletion(status);
		 
         // 存在儲存點,根據我們之前的分析,說明這是一個巢狀呼叫的事務
         // 並且內部事務的傳播級別為nested
         if (status.hasSavepoint()) {
            // 這裡會回滾到定義的儲存點
            status.rollbackToHeldSavepoint();
         }
         
         // 根據我們之前的分析有兩種情況會滿足下面這個判斷
         // 1.直接呼叫,傳播級別為nested、required、requires_new
         // 2.巢狀呼叫,並且內部事務的傳播級別為requires_new
         else if (status.isNewTransaction()) {
            // 直接獲取當前執行緒上繫結的資料庫連線並呼叫其rollback方法
            doRollback(status);
         }
         else {
            // 到這裡說明存在事務,但是不是一個新事務並且沒有儲存點
            // 也就是巢狀呼叫並且內部事務的傳播級別為supports、required、mandatory
            if (status.hasTransaction()) {
                // status.isLocalRollbackOnly,代表事務的結果只能為回滾
                // 預設是false的,在整個流程中沒有看到修改這個屬性
                // isGlobalRollbackOnParticipationFailure
                // 這個屬性的含義是在加入的事務失敗時是否回滾整個事務,預設為true
               if (status.isLocalRollbackOnly() || isGlobalRollbackOnParticipationFailure()) {
                  // 從這裡可以看出,但內部的事務發生異常時會將整個大事務標記成回滾
                  doSetRollbackOnly(status);
               }
               else {
                  // 進入這個判斷說明修改了全域性配置isGlobalRollbackOnParticipationFailure
                  // 內部事務異常並不影響外部事務
               }
            }
            else {
                // 不存在事務,回滾不做任何操作
               logger.debug("Should roll back transaction but cannot - no transaction available");
            }
            // isFailEarlyOnGlobalRollbackOnly這個引數預設為false
            // unexpectedRollback的值一開始就被賦值成了false
            if (!isFailEarlyOnGlobalRollbackOnly()) {
               unexpectedRollback = false;
            }
         }
      }
      catch (RuntimeException | Error ex) {
         triggerAfterCompletion(status, TransactionSynchronization.STATUS_UNKNOWN);
         throw ex;
      }
	  // 觸發同步回撥
      triggerAfterCompletion(status, TransactionSynchronization.STATUS_ROLLED_BACK);

      // unexpectedRollback是false
      // 這個值如果為true,說明
      if (unexpectedRollback) {
         throw new UnexpectedRollbackException(
               "Transaction rolled back because it has been marked as rollback-only");
      }
   }
   finally {
      // 在事務完成後需要執行一些清理動作
      cleanupAfterCompletion(status);
   }
}

上面的程式碼結合註釋看起來應該都非常簡單,我們最後關注一下cleanupAfterCompletion這個方法,對應程式碼如下

private void cleanupAfterCompletion(DefaultTransactionStatus status) {
    
    // 將事務狀態修改為已完成
    status.setCompleted();
    
    // 是否是新的同步
    // 清理掉執行緒繫結的所有同步資訊
    // 直接呼叫時,在任意傳播級別下這個條件都是滿足的
    // 巢狀呼叫時,只有傳播級別為not_supported、requires_new才會滿足
    if (status.isNewSynchronization()) {
        TransactionSynchronizationManager.clear();
    }
    
    // 是否是一個新的事務
    // 直接呼叫下,required、requires_new、nested都是新開的一個事務
    // 巢狀呼叫下,只有requires_new會新起一個事務
    if (status.isNewTransaction()) {
        // 真正執行清理
        doCleanupAfterCompletion(status.getTransaction());
    }
    
    // 如果存在掛起的資源,將掛起的資源恢復
    // 恢復的操作跟掛起的操作正好相反
    // 就是將之前從執行緒解綁的資源(資料庫連線等)已經同步回撥重新繫結到執行緒上
    if (status.getSuspendedResources() != null) {
        if (status.isDebug()) {
            logger.debug("Resuming suspended transaction after completion of inner transaction");
        }
        Object transaction = (status.hasTransaction() ? status.getTransaction() : null);
        resume(transaction, (SuspendedResourcesHolder) status.getSuspendedResources());
    }
}

再來看看doCleanupAfterCompletion到底做了什麼,原始碼如下:

protected void doCleanupAfterCompletion(Object transaction) {
    DataSourceTransactionObject txObject = (DataSourceTransactionObject) transaction;
	
    // 先判斷是否是一個新連線
    // 直接呼叫,如果真正開啟了一個事務必定是個連線
    // 但是巢狀呼叫時,只有requires_new會新起一個連線,其餘的都是複用外部事務的連線
    // 這種情況下不能將連線從執行緒上下文中清除,因為外部事務還需要使用
    if (txObject.isNewConnectionHolder()) {
        TransactionSynchronizationManager.unbindResource(obtainDataSource());
    }
	
    // 恢復連線的狀態
    // 1.重新將連線設定為自動提交
    // 2.恢復隔離級別
    // 3.將read only重新設定為false
    Connection con = txObject.getConnectionHolder().getConnection();
    try {
        if (txObject.isMustRestoreAutoCommit()) {
            con.setAutoCommit(true);
        }
        DataSourceUtils.resetConnectionAfterTransaction(
            con, txObject.getPreviousIsolationLevel(), txObject.isReadOnly());
    }
    catch (Throwable ex) {
        logger.debug("Could not reset JDBC Connection after transaction", ex);
    }
	
    // 最後,因為事務已經完成了所以歸還連線
    if (txObject.isNewConnectionHolder()) {
        DataSourceUtils.releaseConnection(con, this.dataSource);
    }
	
    // 將事務物件中繫結的連線相關資訊也清空掉
    txObject.getConnectionHolder().clear();
}

5、提交事務

提交事務有兩種情況

  1. 正常執行完成,提交事務
  2. 出現異常,但是不滿足回滾條件,仍然提交事務

但是不管哪種清空最終都會呼叫AbstractPlatformTransactionManager#commit方法,所以我們就直接分析這個方法

程式碼如下:

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");
    }
    // 這裡會檢查事務的狀態是否被設定成了只能回滾
    // 這裡檢查的是事務狀態物件中的rollbackOnly屬性
    DefaultTransactionStatus defStatus = (DefaultTransactionStatus) status;
    if (defStatus.isLocalRollbackOnly()) {
        processRollback(defStatus, false);
        return;
    }

    // 這裡會檢查事務物件本身是否被設定成了rollbackOnly
    // 之前我們在分析回滾的程式碼時知道,當內部的事務發生回滾時(supports、required)
    // 預設情況下會將整個事務物件標記為回滾,實際上在外部事務提交時就會進入這個判斷
    // shouldCommitOnGlobalRollbackOnly:在全域性被標記成回滾時是否還要提交,預設為false
    if (!shouldCommitOnGlobalRollbackOnly() && defStatus.isGlobalRollbackOnly()) {
        processRollback(defStatus, true);
        return;
    }
    // 真正處理提交
    processCommit(defStatus);
}

可以看到,即使進入了提交事務的邏輯還是可能回滾。

  1. 事務狀態物件(TransactionStatus)中的狀態被設定成了rollbackOnly
  2. 事務物件本身(DataSourceTransactionObject)被設定成了rollbackOnly

二者在回滾時都是呼叫了processRollback方法,但是稍有區別,通過事務狀態物件造成的回滾最終在回滾後並不會丟擲異常,但是事務物件本身會丟擲異常給呼叫者。

真正處理提交的邏輯在processCommit方法中,程式碼如下:

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();
            }
            // 直接呼叫,傳播級別為required、nested、requires_new
            // 巢狀呼叫,且傳播級別為requires_new
            else if (status.isNewTransaction()) {
                if (status.isDebug()) {
                    logger.debug("Initiating transaction commit");
                }
                // 雖然前面已經檢查過rollbackOnly了,在shouldCommitOnGlobalRollbackOnly為true時
                // 仍然需要提交事務,將unexpectedRollback設定為true,意味著提交事務後仍要丟擲異常
                unexpectedRollback = status.isGlobalRollbackOnly();
                
                // 提交的邏輯很簡單,呼叫了connection的commit方法
                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) {
            // unexpectedRollback為true時,進入這個catch塊
            // 觸發同步回撥
            triggerAfterCompletion(status, TransactionSynchronization.STATUS_ROLLED_BACK);
            throw ex;
        }
        catch (TransactionException ex) {
            // connection的doCommit方法丟擲SqlException時進入這裡
            if (isRollbackOnCommitFailure()) {
                // 在doCommit方法失敗後是否進行回滾,預設為false
                doRollbackOnCommitException(status, ex);
            }
            else {
               // 觸發同步回撥
                triggerAfterCompletion(status, TransactionSynchronization.STATUS_UNKNOWN);
            }
            throw ex;
        }
        catch (RuntimeException | Error ex) {
            // 剩餘其它異常進入這個catch塊
            if (!beforeCompletionInvoked) {
                triggerBeforeCompletion(status);
            }
            doRollbackOnCommitException(status, ex);
            throw ex;
        }

	   // 觸發同步回撥
        try {
            triggerAfterCommit(status);
        }
        finally {
            triggerAfterCompletion(status, TransactionSynchronization.STATUS_COMMITTED);
        }

    }
    finally {
        // 跟回滾時一樣,做一些清理動作
        cleanupAfterCompletion(status);
    }
}

整個邏輯還是比較簡單的,最核心的步驟就是呼叫了資料庫連線物件(Connection)的commit方法。

總結

本文主要分析了Spring中事務的實現機制,從事務實現的入口---->EnableTransactionManagement註解,到事務實現的核心TransactionInterceptor都做了詳細的分析。其中最複雜的一塊在於事務的建立,也就是createTransactionIfNecessary,既要考慮直接呼叫的情況,也要考慮巢狀呼叫的情況,並且還需要針對不同的傳播級別做不同的處理。對於整個事務管理,我們需要分清楚下面這幾個物件的含義

  1. TransactionDefinition
  2. TransactionStatus
  3. DataSourceTransactionObject
  4. TransactionInfo

TransactionDefinition代表的是我們通過@Transactional註解指定的事務的屬性,包括是否只讀、超時、回滾等。

TransactionStatus代表的是事務的狀態,這個狀態分為很多方面,比如事務的執行狀態(是否完成、是否被標記為rollbakOnly),事務的同步狀態(這個事務是否開啟了一個新的同步),還有事務是否設定了儲存點。更準確的來說,TransactionStatus代表的是被@Transactional註解修飾的方法的狀態,只要被@Transactional註解修飾了,在執行通知邏輯時就會生成一個TransactionStatus物件,即使這個方法沒有真正的開啟一個資料庫事務

DataSourceTransactionObject代表的是一個資料庫事務物件,實際上儲存的就是一個資料庫連線以及這個連線的狀態。

TransactionInfo代表的是事務相關的所有資訊,組合了事務管理器,事務狀態,事務定義並持有一箇舊的TransactionInfo引用,這個物件在事務管理的流程中其實沒有實際的作用,主要的目的是為了讓我們在事務的執行過程中獲取到事務的相關資訊,我們可以直接呼叫TransactionAspectSupportcurrentTransactionInfo方法獲取到當前執行緒繫結的TransactionInfo物件。

另外,看完本文希望大家對於事務的傳播機制能有更深一點的認知,而不是停留在概念上,要講清楚事務的傳播機制我們先做以下定。

  1. 凡是在執行代理方法的過程中從資料來源重新獲取了連線並呼叫了其setAtuoCommit(false)方法,而且還將這個連線繫結到了執行緒上下文中,我們就認為它新建了一個事務。注意了,有三個要素

    • 連線是從資料來源中獲取到的

    • 呼叫了連線的setAtuoCommit(false)方法

    • 這個連線被繫結到了執行緒上下文中

  2. 凡是在執行代理方法的過程中,掛起了外部事務,並且沒有新建事務,那麼我們認為這個這個方法是以非事務的方式執行的。

  3. 凡是在執行代理方法的過程中,沒有掛起外部事務,但是也沒有新建事務,那麼我們認為這個方法加入了外部的事務

掛起事務的表現在於清空了執行緒上下文中繫結的連線!

同時我們分為兩種情況討論來分析傳播機制

  • 直接呼叫
  • 間接呼叫

直接呼叫不存在加入外部事務這麼一說,要麼就是新建事務,要麼就是以非事務的方式執行,當然,也可能丟擲異常。

傳播級別 執行方式
requires_new 新建事務
nested 新建事務
required 新建事務
supports 非事務的方式執行
not_supported 非事務的方式執行
never 非事務的方式執行
mandatory 丟擲異常

間接呼叫時我們要分兩種情況討論

  1. 外部的方法新建了事務
  2. 外部的方法本身就是非事務的方式執行的

在外部方法新建事務的情況下時(說明外部事務的傳播級別為requires_new,nested或者required),當前方法的執行方式如下表所示

傳播級別 執行方式
requires_new 新建事務
nested
required 直接加入到外部事務
supports 直接加入到外部事務
not_supported 掛起外部事務,以非事務的方式執行
never 丟擲異常
mandatory 直接加入到外部事務

當外部方法沒有新建事務時,其實它的執行方式跟直接呼叫是一樣的,這裡就不贅述了。

本文到這裡就結束了,文章很長,希望你可以耐心看完哈~

碼到手痠,求個三連啊!啊!啊!

如果本文對你由幫助的話,記得點個贊吧!也歡迎關注我的公眾號,微信搜尋:程式設計師DMZ,或者掃描下方二維碼,跟著我一起認認真真學Java,踏踏實實做一個coder。

公眾號

我叫DMZ,一個在學習路上匍匐前行的小菜鳥!

相關文章