1. 程式人生 > >Spring事務專題(五)聊聊Spring事務到底是如何實現的

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

# 前言 本專題大綱: ![image-20200809182052520](https://gitee.com/wx_cc347be696/blogImage/raw/master/image-20200809182052520.png) 本文為本專題倒數第二篇文章。 在[上篇文章](http://mp.weixin.qq.com/s?__biz=MzU5ODg2Njk4OA==&mid=2247484660&idx=1&sn=15d22796dd838116c46135e79414dc06&chksm=febce9b7c9cb60a12a90fe8e4dffb20dc380dc7c841228c02d3ee0230bd32014376c5ef3fd2a#rd)中我們一起學習了Spring中的事務抽象機制以及動手模擬了一下Spring中的事務管理機制,那麼本文我們就通過原始碼來分析一下Spring中的事務管理到底是如何實現的,本文將選用`Spring5.2.x`版本。 # 從@EnableTransactionManagement開始 Spring事務管理的入口就是`@EnableTransactionManagement`註解,所以我們直接從這個註解入手,其原始碼如下: ```java public @interface EnableTransactionManagement { // 是否使用cglib代理,預設是jdk代理 boolean proxyTargetClass() default false; // 使用哪種代理模式,Spring AOP還是AspectJ AdviceMode mode() default AdviceMode.PROXY; // 為了完成事務管理,會向容器中新增通知 // 這個order屬性代表了通知的執行優先順序 // 預設是最低優先順序 int order() default Ordered.LOWEST_PRECEDENCE; } ``` 需要注意的是,`@EnableTransactionManagement`的`proxyTargetClass`會影響Spring中所有通過自動代理生成的物件。如果將`proxyTargetClass`設定為true,那麼意味通過`@EnableAspectJAutoProxy`所生成的代理物件也會使用cglib進行代理。關於`@EnableTransactionManagement`跟`@EnableAspectJAutoProxy`混用時的一些問題等我們在對`@EnableTransactionManagement`有一定了解後再專門做一個比較,現在我們先來看看這個註解到底在做了什麼? ![事務註解分析](https://gitee.com/wx_cc347be696/blogImage/raw/master/事務註解分析.png) 從上圖中可以看出這個註解做的就是向容器中註冊了`AutoProxyRegistrar`跟一個`ProxyTransactionManagementConfiguration`(*這裡就不考慮AspectJ了,我們平常都是使用SpringAOP*), `AutoProxyRegistrar`用於開啟自動代理,其原始碼如下: # AutoProxyRegistrar分析 這個類實現了`ImportBeanDefinitionRegistrar`,它的作用是向容器中註冊別的`BeanDefinition`,我們直接關注它的`registerBeanDefinitions`方法即可 ```java // AnnotationMetadata,代表的是AutoProxyRegistrar的匯入類的元資訊 // 既包含了類元資訊,也包含了註解元資訊 public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) { boolean candidateFound = false; // 獲取@EnableTransactionManagement所在配置類上的註解元資訊 Set 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`,其原始碼如下 ```java public static BeanDefinition registerAspectJAnnotationAutoProxyCreatorIfNecessary( BeanDefinitionRegistry registry, @Nullable Object source) { return registerOrEscalateApcAsRequired(AnnotationAwareAspectJAutoProxyCreator.class, registry, source); } ``` `@EnableTransactionManagement`最終呼叫的是,`AopConfigUtils#registerAutoProxyCreatorIfNecessary`,其原始碼如下 ```java public static BeanDefinition registerAutoProxyCreatorIfNecessary( BeanDefinitionRegistry registry, @Nullable Object source) { return registerOrEscalateApcAsRequired(InfrastructureAdvisorAutoProxyCreator.class, registry, source); } ``` 它們最終都會呼叫`registerOrEscalateApcAsRequired`方法,只不過傳入的引數不一樣而已,一個是`AnnotationAwareAspectJAutoProxyCreator`,另一個是`InfrastructureAdvisorAutoProxyCreator`。 `registerOrEscalateApcAsRequired`原始碼如下: ```java 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; } ``` `InfrastructureAdvisorAutoProxyCreator`跟`AnnotationAwareAspectJAutoProxyCreator`的優先順序是如何定義的呢?我們來看看`AopConfigUtils`這個類中的一個靜態程式碼塊 ```java 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](https://gitee.com/wx_cc347be696/blogImage/raw/master/ProxyTransactionManagementConfiguration.png) 這個類的繼承關係還是很簡單的,只有一個父類`AbstractTransactionManagementConfiguration` ### AbstractTransactionManagementConfiguration 原始碼如下: ```java @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 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`註解的,我們來看一個實際使用的例子 ```java @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`註解,如下: ```java @Component public class DmzListener { // 新增一個監聽器 @EventListener public void listen(DmzTransactionEvent transactionEvent){ System.out.println("事務已提交"); } } ``` 這個時候程式的輸出就會是 ```java // 事務已提交 // sql invoked ``` 那麼`@TransactionalEventListener`註解是實現這種**看似非同步**(實際上並不是)的監聽方式的呢? ![TransactionalEventListenerFactory呼叫鏈](https://gitee.com/wx_cc347be696/blogImage/raw/master/TransactionalEventListenerFactory呼叫鏈.png) 大家按照上面這個呼叫鏈可以找到這麼一段程式碼 ![image-20200810180616284](https://gitee.com/wx_cc347be696/blogImage/raw/master/image-20200810180616284.png) 通過上面的程式碼,我們可以發現最終會呼叫到`TransactionalEventListenerFactory`的`createApplicationListener`方法,通過這個方法建立一個事件監聽器然後新增到容器中,`createApplicationListener`方法原始碼如下: ```java public ApplicationListener createApplicationListener(String beanName, Class type, Method method) { return new ApplicationListenerMethodTransactionalAdapter(beanName, type, method); } ``` 就是建立了一個`ApplicationListenerMethodTransactionalAdapter`,這個類本身就是一個事件監聽器(實現了`ApplicationListener`介面)。我們直接關注它的事件監聽方法,也就是`onApplicationEvent`方法,其原始碼如下: ```java @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`,這個類的原始碼非常簡單,這裡就單獨取它一個方法看下 ```java // 這個方法會在事務提交前執行 public void beforeCommit(boolean readOnly) { // 在執行時會先判斷在@TransactionalEventListener註解中定義的phase是不是BEFORE_COMMIT // 如果不是的話,什麼事情都不做 if (this.phase == TransactionPhase.BEFORE_COMMIT) { processEvent(); } } ``` ----- 別看上面這麼多內容,到目前為止我們還是隻對`ProxyTransactionManagementConfiguration`的父類做了介紹,接下來我們就來看看`ProxyTransactionManagementConfiguration`自身做了什麼事情。 ## 原始碼分析 ```java // 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.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`方法,我們直接定位到這個方法的具體實現邏輯 ```java // 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. 通過回撥實現事務管理 這裡我們只分析`標準事務管理`,下面的原始碼也只保留標準事務管理相關程式碼 ```java 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、獲取事務屬性 ```java // 獲取事務對應的屬性,實際上返回一個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`方法,其程式碼如下: ```java 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)`這個方法中 整個呼叫鏈我這裡也畫出來了,感興趣的大家可以跟一下 ![事務註解解析呼叫棧](https://gitee.com/wx_cc347be696/blogImage/raw/master/事務註解解析呼叫棧.png) 對於本文,我們直接定位到最後一步,對應原始碼如下: ```java 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 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、 建立事務 對應程式碼如下: ```java 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實現事務管理裡面最難的了,因為牽涉到事務的傳播機制,不同傳播級別是如何進行處理的就是下面這段程式碼決定的,比較難,希望大家能耐心看完 ```java 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. 在一個需要事務管理的方法中呼叫了另外一個需要事務管理的方法(巢狀呼叫) 用程式碼表示如下: ```java @Service public class IndexService { @Autowired DmzService dmzService; // 直接呼叫 @Transactional public void directTransaction(){ // ...... } // 巢狀呼叫 @Transactional public void nestedTransaction(){ dmzService.directTransaction(); } } ``` >
ps:我們暫且不考慮自呼叫的情況,因為自呼叫可能會出現事務失效,在下篇文章我們專門來聊一聊事務管理中的那些坑 ##### 直接呼叫 我們先來看看直接呼叫的情況下上述程式碼時如何執行的 ![直接呼叫事務方法流程](https://gitee.com/wx_cc347be696/blogImage/raw/master/直接呼叫事務方法流程.png) 1. `doGetTransaction`原始碼分析 ```java 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`。 2. `isExistingTransaction`原始碼分析 ```java protected boolean isExistingTransaction(Object transaction) { DataSourceTransactionObject txObject = (DataSourceTransactionObject) transaction; // 只有在存在連線並且連線上已經激活了事務才會返回true return (txObject.hasConnectionHolder() && txObject.getConnectionHolder().isTransactionActive()); } ``` 直接呼叫的情況下,這裡肯定是返回fasle 3. 從這裡可以看出,如果直接呼叫了一個傳播級別為`MANDATORY`的方法將會丟擲異常 4. 傳播級別為`required、requires_new、nested`時,會真正開啟事務,對應程式碼如下 ```java 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; } } ``` 我們先來看看它的掛起操作幹了什麼,對應程式碼如下: ```java // 直接呼叫時,傳入的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`方法,對應程式碼如下: ```java 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`的建構函式,我們來看一看每個引數的含義以及實際傳入的是什麼。對應程式碼如下: ```java // 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。對應程式碼如下: ```java 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`,這個方法的主要目的就是為了準備這個事務需要的同步,對應原始碼如下: ```java 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`),對應程式碼如下: ```java 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`程式碼如下 ```java // 傳入的引數為: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`不會真正開啟一個事務。 雖然不會真正開啟一個事務,只是開啟了一個“空事務”,但是當這個空事務完成時仍然會觸發註冊的回撥。 ##### 巢狀呼叫 ![巢狀呼叫流程](https://gitee.com/wx_cc347be696/blogImage/raw/master/巢狀呼叫流程.png) 前面已經介紹了在直接呼叫下七種不同隔離級別在建立事務時的不同表現,程式碼看似很多,實際還是比較簡單的,接下來我們要介紹的就是`巢狀呼叫`,也就是已經存在事務的情況下,呼叫了另外一個被事務管理的方法(並且事務管理是生效的)。我們需要關注的是下面這段程式碼 ```java 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`這個方法做了什麼,程式碼如下: ```java protected boolean isExistingTransaction(Object transaction) { DataSourceTransactionObject txObject = (DataSourceTransactionObject) transaction; // 首先判斷txObject中是否已經綁定了連線 // 其次判斷這個連線上是否已經激活了事務 return (txObject.hasConnectionHolder() && txObject.getConnectionHolder().isTransactionActive()); } ``` 結合我們之前分析的直接呼叫的程式碼邏輯,可以看出,只有外圍的事務的傳播級別為`required、requires_new、nested`時,這個判斷才會成立。因為只有在這些傳播級別下才會真正的開啟事務,才會將連線繫結到當前執行緒,`doGetTransaction`方法才能返回一個已經綁定了資料庫連線的事務物件。 在滿足了`isExistingTransaction`時,會進入巢狀呼叫的處理邏輯,也就是`handleExistingTransaction`方法,其程式碼如下: >
程式碼很長,但是大家跟著我一步步梳理就可以了,其實邏輯也不算特別複雜 > > 整個程式碼邏輯其實就是為了實現事務的傳播,在不同的傳播級別下做不同的事情 ```java 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`時會進入下面這段程式碼 ```java // 如果巢狀的事務的傳播級別為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. 掛起是因為將來還要恢復,所以不能單純的只是清空呀,還得將清空的資訊儲存到當前的事務上,這樣噹噹前的事務完成後可以恢復到掛起時的狀態,以便於外圍的事務能夠正確的執行下去 其中第一步對應的程式碼如下: ```java protected final SuspendedResourcesHolder suspend(@Nullable Object transaction) throws TransactionException { // 巢狀呼叫下,同步是已經被激活了的 if (TransactionSynchronizationManager.isSynchronizationActive()) { // 解綁執行緒上繫結的同步回撥,並返回 List 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`,這個方法我們之前分析過了,但是在這裡傳入的引數是不同的 ```java // 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`時,會進入下面這段程式碼 ```java 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`進入下面這段程式碼 ```java 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`這幾種傳播級別都會進入下面這段程式碼 ```java // 省略了校驗相關程式碼,前面已經介紹過了,預設是關閉校驗的 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`稍微有