Spring事務原理一探

 
指「務」
以MySQL、Spring
 
注[客](https://yunxin.163.com
/dev-blog/?from=juejin&utm_source=juejin&utm_medium=article&)
 
解[信](https://yunxin.163.com/?from=juejin&utm_source=juejin&utm_medi
um=article&)
 
##
 
### ACID
 
的ACID
 
+ (Atomicity
+ (Consistency
滿
 
 
 
 

歸納來說,事務是一個由有限操做集合組成的邏輯單元。事務操做包含兩個目的,數據一致以及操做隔離。數據一致是指事務提交時保證事務內的全部操做都成功完成,而且更改永久生效;事務回滾時,保證可以恢復到事務執行以前的狀態。操做隔離則是指多個同時執行的事務之間應該相互獨立,互不影響。html

事務是一個比較普遍的概念,事務管理資源除了咱們熟知的數據庫外,還能夠包含消息隊列、文件系統等。固然,通常來講,咱們說的事務單指「數據庫事務」。接下來咱們會以MySQL數據庫、Spring聲明式事務爲主要研究對象,可是不少事務概念、接口抽象和實現方式同時適用於其餘狀況。java

想要閱讀更多技術乾貨、行業洞察,歡迎關注網易雲信博客web

瞭解網易雲信,來自網易核心架構的通訊與視頻雲服務。spring

事務屬性和行爲

ACID屬性

提到事務,不可避免須要涉及到事務的ACID屬性:數據庫

  • 原子性(Atomicity):事務做爲一個總體被執行,包含在其中的對數據庫的操做要麼所有被執行,要麼都不執行。
  • 一致性(Consistency):事務應確保數據庫的狀態從一個一致狀態轉變爲另外一個一致狀態。一致狀態的含義是數據庫中的數據應知足完整性約束。
  • 隔離性(Isolation):多個事務併發執行時,一個事務的執行不該影響其餘事務的執行。
  • 持久性(Durability):已被提交的事務對數據庫的修改應該永久保存在數據庫中。

咱們將嚴格遵循ACID屬性的事務稱爲剛性事務。與之相對,指望最終一致性,在事務執行的中間狀態容許暫時不遵循ACID屬性的事務稱爲柔性事務,可參考傳統事務與柔性事務,柔性事務的使用涉及到分佈式事務方案,能夠後續擴展,這裏咱們先將注意集中在事務實現原理上。編程

隔離級別

根據SQL92標準,MySQL的InnoDB引擎提供四種隔離級別(即ACID中的I):讀未提交(READ UNCOMMITTED)、讀已提交(READ COMMITTED)、可重複讀(REPEATABLE READ)和串行化(SERIALIZABLE),InnoDB默認的隔離級別是REPEATABLE READ,其可避免髒讀不可重複讀,但不能避免幻讀,須要指出的是,InnoDB引擎的多版本併發控制機制(MVCC)並無徹底避免幻讀,關於該問題以及隔離級別說明,可參考MySQL的InnoDB的幻讀問題markdown

傳播機制

Spring針對方法嵌套調用時事務的建立行爲定義了七種事務傳播機制,分別是PROPAGATION_REQUIRED、PROPAGATION_SUPPORT、PROPAGATION_MANDATORY、PROPAGATION_REQUIRES_NEW、PROPAGATION_NOT_SUPPORTED、PROPAGATION_NEVER以及PROPAGATION_NESTED,基本上從字面意思就能知道每種傳播機制各自的行爲表現,Spring默認的事務傳播機制是PROPAGATION_REQUIRED,即若是當前存在事務,則使用當前事務,不然建立新的事務。詳情可參考Spring事務傳播行爲架構

事務行爲

事務的行爲包括事務開啓、事務提交和事務回滾。InnoDB全部的用戶SQL執行都在事務控制以內,在默認狀況下,autocommit設置爲true,單條SQL執行成功後,MySQL會自動提交事務,或者若是SQL執行出錯,則根據異常類型執行事務提交或者回滾。可使用START TRANSACTION(SQL標準)或者BEGIN開啓事務,使用COMMITROLLBACK提交和回滾事務;也能夠經過設置autocommit屬性來控制事務行爲,當設置autocommit爲false時,其後執行的多條SQL語句將在一個事務內,直到執行COMMIT或者ROLLBACK事務纔會提交或者回滾。併發

AOP加強

Spring使用AOP(面向切面編程)來實現聲明式事務,後續在講Spring事務具體實現的時候會詳細說明,關於AOP的概念可參考Spring AOP概念理解(通俗易懂),這裏再也不細說。說下動態代理和AOP加強。app

動態代理是Spring實現AOP的默認方式,分爲兩種:JDK動態代理和CGLIB動態代理。JDK動態代理面向接口,經過反射生成目標代理接口的匿名實現類;CGLIB動態代理則經過繼承,使用字節碼加強技術(或者objenesis類庫)爲目標代理類生成代理子類。Spring默認對接口實現使用JDK動態代理,對具體類使用CGLIB,同時也支持配置全局使用CGLIB來生成代理對象。

咱們在切面配置中會使用到@Aspect註解,這裏用到了Aspectj的切面表達式。Aspectj是java語言實現的一個AOP框架,使用靜態代理模式,擁有完善的AOP功能,與Spring AOP互爲補充。Spring採用了Aspectj強大的切面表達式定義方式,可是默認狀況下仍然使用動態代理方式,並未使用Aspectj的編譯器和織入器,固然也支持配置使用Aspectj靜態代理替代動態代理方式。Aspectj功能更強大,比方說它支持對字段、POJO類進行加強,與之相對,Spring只支持對Bean方法級別進行加強。

Spring對方法的加強有五種方式:

  • 前置加強(org.springframework.aop.BeforeAdvice):在目標方法執行以前進行加強;
  • 後置加強(org.springframework.aop.AfterReturningAdvice):在目標方法執行以後進行加強;
  • 環繞加強(org.aopalliance.intercept.MethodInterceptor):在目標方法執行先後都執行加強;
  • 異常拋出加強(org.springframework.aop.ThrowsAdvice):在目標方法拋出異常後執行加強;
  • 引介加強(org.springframework.aop.IntroductionInterceptor):爲目標類添加新的方法和屬性。

聲明式事務的實現就是經過環繞加強的方式,在目標方法執行以前開啓事務,在目標方法執行以後提交或者回滾事務,事務攔截器的繼承關係圖能夠體現這一點:

 

 

Spring事務抽象

統一一致的事務抽象是Spring框架的一大優點,不管是全局事務仍是本地事務,JTA、JDBC、Hibernate仍是JPA,Spring都使用統一的編程模型,使得應用程序能夠很容易地在全局事務與本地事務,或者不一樣的事務框架之間進行切換。下圖是Spring事務抽象的核心類圖:

 

 

接口PlatformTransactionManager定義了事務操做的行爲,其依賴TransactionDefinitionTransactionStatus接口,其實大部分的事務屬性和行爲咱們以MySQL數據庫爲例已經有過了解,這裏再對應介紹下。

  • PlatformTransactionManager:事務管理器
    • getTransaction方法:事務獲取操做,根據事務屬性定義,獲取當前事務或者建立新事物;
    • commit方法:事務提交操做,注意這裏所說的提交併不是直接提交事務,而是根據當前事務狀態執行提交或者回滾操做;
    • rollback方法:事務回滾操做,一樣,也並不是必定直接回滾事務,也有可能只是標記事務爲只讀,等待其餘調用方執行回滾。
  • TransactionDefinition:事務屬性定義
    • getPropagationBehavior方法:返回事務的傳播屬性,默認是PROPAGATION_REQUIRED
    • getIsolationLevel方法:返回事務隔離級別,事務隔離級別只有在建立新事務時纔有效,也就是說只對應傳播屬性PROPAGATION_REQUIREDPROPAGATION_REQUIRES_NEW
    • getTimeout方法:返回事務超時時間,以秒爲單位,一樣只有在建立新事務時纔有效;
    • isReadOnly方法:是否優化爲只讀事務,支持這項屬性的事務管理器會將事務標記爲只讀,只讀事務不容許有寫操做,不支持只讀屬性的事務管理器須要忽略這項設置,這一點跟其餘事務屬性定義不一樣,針對其餘不支持的屬性設置,事務管理器應該拋出異常。
    • getName方法:返回事務名稱,聲明式事務中默認值爲「類的徹底限定名.方法名」。
  • TransactionStatus:當前事務狀態
    • isNewTransaction方法:當前方法是否建立了新事務(區別於使用現有事務以及沒有事務);
    • hasSavepoint方法:在嵌套事務場景中,判斷當前事務是否包含保存點;
    • setRollbackOnlyisRollbackOnly方法:只讀屬性設置(主要用於標記事務,等待回滾)和查詢;
    • flush方法:刷新底層會話中的修改到數據庫,通常用於刷新如Hibernate/JPA的會話,是否生效由具體事務資源實現決定;
    • isCompleted方法:判斷當前事務是否已完成(已提交或者已回滾)。

部分Spring包含的對PlatformTransactionManager的實現類以下圖所示:

 

 

AbstractPlatformTransactionManager抽象類實現了Spring事務的標準流程,其子類DataSourceTransactionManager是咱們使用較多的JDBC單數據源事務管理器,而JtaTransactionManager是JTA(Java Transaction API)規範的實現類,另外兩個則分別是JavaEE容器WebLogicWebSphere的JTA事務管理器的具體實現。

Spring事務切面

以前提到,Spring採用AOP來實現聲明式事務,那麼事務的AOP切面是如何織入的呢?這一點涉及到AOP動態代理對象的生成過程。

代理對象生成的核心類是AbstractAutoProxyCreator,實現了BeanPostProcessor接口,會在Bean初始化完成以後,經過postProcessAfterInitialization方法生成代理對象,關於BeanPostProcessor在Bean生命週期中的做用,可參考一些經常使用的Spring擴展接口

看一下AbstractAutoProxyCreator類的核心代碼,主要關注三個方法:postProcessAfterInitialization、wrapIfNecessary和createProxy,爲了突出核心流程,以註釋代替了部分代碼的具體實現,後續的源碼分析也採用相同的處理。

// AbstractAutoProxyCreator.class @Override public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { if (bean != null) { Object cacheKey = getCacheKey(bean.getClass(), beanName); if (!this.earlyProxyReferences.contains(cacheKey)) { // 建立代理對象 return wrapIfNecessary(bean, beanName, cacheKey); } } return bean; } protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) { // 參數檢查,跳過已經執行過代理對象生成,或者已知的不須要生成代理對象的Bean ... // Create proxy if we have advice. // 查詢當前Bean全部的AOP加強配置,最終是經過AOPUtils工具類實現 Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null); if (specificInterceptors != DO_NOT_PROXY) { this.advisedBeans.put(cacheKey, Boolean.TRUE); // 執行AOP織入,建立代理對象 Object proxy = createProxy( bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean)); this.proxyTypes.put(cacheKey, proxy.getClass()); return proxy; } this.advisedBeans.put(cacheKey, Boolean.FALSE); return bean; } protected Object createProxy(Class<?> beanClass, String beanName, Object[] specificInterceptors, TargetSource targetSource) { if (this.beanFactory instanceof ConfigurableListableBeanFactory) { AutoProxyUtils.exposeTargetClass((ConfigurableListableBeanFactory) this.beanFactory, beanName, beanClass); } // 實例化代理工廠類 ProxyFactory proxyFactory = new ProxyFactory(); proxyFactory.copyFrom(this); // 當全局使用動態代理時,設置是否須要對目標Bean強制使用CGLIB動態代理 ... // 構建AOP加強顧問,包含框架公共加強和應用程序自定義加強 // 設置proxyFactory屬性,如加強、目標類、是否容許變動等 ... // 建立代理對象 return proxyFactory.getProxy(getProxyClassLoader()); } 

最後是經過調用ProxyFactory#getProxy(java.lang.ClassLoader)方法來建立代理對象:

// ProxyFactory.class public Object getProxy(ClassLoader classLoader) { return createAopProxy().getProxy(classLoader); } // ProxyFactory父類ProxyCreatorSupport.class protected final synchronized AopProxy createAopProxy() { if (!this.active) { activate(); } return getAopProxyFactory().createAopProxy(this); } public ProxyCreatorSupport() { this.aopProxyFactory = new DefaultAopProxyFactory(); } 

ProxyFactory的父類構造器實例化了DefaultAopProxyFactory類,從其源代碼咱們能夠看到Spring動態代理方式選擇策略的實現:若是目標類optimize,proxyTargetClass屬性設置爲true或者未指定須要代理的接口,則使用CGLIB生成代理對象,不然使用JDK動態代理。

public class DefaultAopProxyFactory implements AopProxyFactory, Serializable { @Override public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException { // 若是optimize,proxyTargetClass屬性設置爲true或者未指定代理接口,則使用CGLIB生成代理對象 if (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)) { Class<?> targetClass = config.getTargetClass(); // 參數檢查,targetClass爲空拋出異常 ... // 目標類自己是接口或者代理對象,仍然使用JDK動態代理 if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) { return new JdkDynamicAopProxy(config); } // Objenesis是一個能夠不經過構造器建立子類的java工具類庫 // 做爲Spring 4.0後CGLIB的默認實現 return new ObjenesisCglibAopProxy(config); } else { // 不然使用JDK動態代理 return new JdkDynamicAopProxy(config); } } ... } 

Spring事務攔截

咱們已經瞭解了AOP切面織入生成代理對象的過程,當Bean方法經過代理對象調用時,會觸發對應的AOP加強攔截器,前面提到聲明式事務是一種環繞加強,對應接口爲MethodInterceptor,事務加強對該接口的實現爲TransactionInterceptor,類圖以下:

 

 

事務攔截器TransactionInterceptorinvoke方法中,經過調用父類TransactionAspectSupportinvokeWithinTransaction方法進行事務處理,該方法支持聲明式事務和編程式事務。

// TransactionInterceptor.class @Override public Object invoke(final MethodInvocation invocation) throws Throwable { // 獲取targetClass ... // Adapt to TransactionAspectSupport's invokeWithinTransaction... return invokeWithinTransaction(invocation.getMethod(), targetClass, new InvocationCallback() { @Override public Object proceedWithInvocation() throws Throwable { // 實際執行目標方法 return invocation.proceed(); } }); } // TransactionInterceptor父類TransactionAspectSupport.class protected Object invokeWithinTransaction(Method method, Class<?> targetClass, final InvocationCallback invocation) throws Throwable { // If the transaction attribute is null, the method is non-transactional. // 查詢目標方法事務屬性、肯定事務管理器、構造鏈接點標識(用於確認事務名稱) final TransactionAttribute txAttr = getTransactionAttributeSource().getTransactionAttribute(method, targetClass); final PlatformTransactionManager tm = determineTransactionManager(txAttr); final String joinpointIdentification = methodIdentification(method, targetClass, txAttr); if (txAttr == null || !(tm instanceof CallbackPreferringPlatformTransactionManager)) { // 事務獲取 TransactionInfo txInfo = createTransactionIfNecessary(tm, txAttr, joinpointIdentification); Object retVal = null; try { // 經過回調執行目標方法 retVal = invocation.proceedWithInvocation(); } catch (Throwable ex) { // 目標方法執行拋出異常,根據異常類型執行事務提交或者回滾操做 completeTransactionAfterThrowing(txInfo, ex); throw ex; } finally { // 清理當前線程事務信息 cleanupTransactionInfo(txInfo); } // 目標方法執行成功,提交事務 commitTransactionAfterReturning(txInfo); return retVal; } else { // 帶回調的事務執行處理,通常用於編程式事務 ... } } 

在講Spring事務抽象時,有提到事務抽象的核心接口爲PlatformTransactionManager,它負責管理事務行爲,包括事務的獲取、提交和回滾。在invokeWithinTransaction方法中,咱們能夠看到createTransactionIfNecessarycommitTransactionAfterReturningcompleteTransactionAfterThrowing都是針對該接口編程,並不依賴於特定事務管理器,這裏是對Spring事務抽象的實現。

//TransactionAspectSupport.class protected TransactionInfo createTransactionIfNecessary( PlatformTransactionManager tm, TransactionAttribute txAttr, final String joinpointIdentification) { ... TransactionStatus status = null; if (txAttr != null) { if (tm != null) { // 獲取事務 status = tm.getTransaction(txAttr); ... } protected void commitTransactionAfterReturning(TransactionInfo txInfo) { if (txInfo != null && txInfo.hasTransaction()) { ... // 提交事務 txInfo.getTransactionManager().commit(txInfo.getTransactionStatus()); } } protected void completeTransactionAfterThrowing(TransactionInfo txInfo, Throwable ex) { if (txInfo != null && txInfo.hasTransaction()) { ... if (txInfo.transactionAttribute.rollbackOn(ex)) { try { // 異常類型爲回滾異常,執行事務回滾 txInfo.getTransactionManager().rollback(txInfo.getTransactionStatus()); } ... } else { try { // 異常類型爲非回滾異常,仍然執行事務提交 txInfo.getTransactionManager().commit(txInfo.getTransactionStatus()); } ... } protected final class TransactionInfo { private final PlatformTransactionManager transactionManager; ... 

另外,在獲取事務時,AbstractPlatformTransactionManager#doBegin方法負責開啓新事務,在DataSourceTransactionManager有以下代碼:

@Override protected void doBegin(Object transaction, TransactionDefinition definition) { // 獲取數據庫鏈接con ... if (con.getAutoCommit()) { txObject.setMustRestoreAutoCommit(true); if (logger.isDebugEnabled()) { logger.debug("Switching JDBC Connection [" + con + "] to manual commit"); } con.setAutoCommit(false); } ... } 

這裏才真正開啓了數據庫事務。

Spring事務同步

提到事務傳播機制時,咱們常常提到一個條件「若是當前已有事務」,那麼Spring是如何知道當前是否已經開啓了事務呢?在AbstractPlatformTransactionManager中是這樣作的:

// AbstractPlatformTransactionManager.class @Override public final TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException { Object transaction = doGetTransaction(); // 參數爲null時構造默認值 ... if (isExistingTransaction(transaction)) { // Existing transaction found -> check propagation behavior to find out how to behave. return handleExistingTransaction(definition, transaction, debugEnabled); } ... // 獲取當前事務對象 protected abstract Object doGetTransaction() throws TransactionException; // 判斷當前事務對象是否包含活躍事務 protected boolean isExistingTransaction(Object transaction) throws TransactionException { return false; } 

注意getTransaction方法是final的,沒法被子類覆蓋,保證了獲取事務流程的一致和穩定。抽象方法doGetTransaction獲取當前事務對象,方法isExistingTransaction判斷當前事務對象是否存在活躍事務,具體邏輯由特定事務管理器實現,看下咱們使用最多的DataSourceTransactionManager對應的實現:

// DataSourceTransactionManager.class @Override protected Object doGetTransaction() { DataSourceTransactionObject txObject = new DataSourceTransactionObject(); txObject.setSavepointAllowed(isNestedTransactionAllowed()); ConnectionHolder conHolder = (ConnectionHolder) TransactionSynchronizationManager.getResource(this.dataSource); txObject.setConnectionHolder(conHolder, false); return txObject; } @Override protected boolean isExistingTransaction(Object transaction) { DataSourceTransactionObject txObject = (DataSourceTransactionObject) transaction; return (txObject.hasConnectionHolder() && txObject.getConnectionHolder().isTransactionActive()); } 

能夠看到,獲取當前事務對象時,使用了TransactionSynchronizationManager#getResource方法,類圖以下:

 

 

TransactionSynchronizationManager經過ThreadLocal對象在當前線程記錄了resourcessynchronizations屬性。resources是一個HashMap,用於記錄當前參與事務的事務資源,方便進行事務同步,在DataSourceTransactionManager的例子中就是以dataSource做爲key,保存了數據庫鏈接,這樣在同一個線程中,不一樣的方法調用就能夠經過dataSource獲取相同的數據庫鏈接,從而保證全部操做在一個事務中進行。synchronizations屬性是一個TransactionSynchronization對象的集合,AbstractPlatformTransactionManager類中定義了事務操做各個階段的調用流程,以事務提交爲例:

// AbstractPlatformTransactionManager.class private void processCommit(DefaultTransactionStatus status) throws TransactionException { try { boolean beforeCompletionInvoked = false; try { prepareForCommit(status); triggerBeforeCommit(status); triggerBeforeCompletion(status); .... else if (status.isNewTransaction()) { // 記錄日誌 ... doCommit(status); } ... // 事務調用異常處理 ... try { triggerAfterCommit(status); } finally { triggerAfterCompletion(status, TransactionSynchronization.STATUS_COMMITTED); } } } 

咱們能夠看到,有不少trigger前綴的方法,這些方法用於在事務操做的各個階段觸發回調,從而能夠精確控制在事務執行的不一樣階段所要執行的操做,這些回調實際上都經過TransactionSynchronizationUtils來實現,它會遍歷TransactionSynchronizationManager#synchronizations集合中的TransactionSynchronization對象,而後分別觸發集合中各元素對應方法的調用。例如:

TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronizationAdapter() { @Override public void afterCommit() { // do something after commit } }); 

這段代碼就在當前線程的事務synchronizations屬性中,添加了一個自定義同步類,若是當前存在事務,那麼在事務管理器執行事務提交以後,就會觸發afterCommit方法,能夠經過這種方式在事務執行的不一樣階段自定義一些操做。

到這裏,咱們已經對Spring事務的實現原理和處理流程有了必定的瞭解。

想要閱讀更多技術乾貨、行業洞察,歡迎關注網易雲信博客

瞭解網易雲信,來自網易核心架構的通訊與視頻雲服務。

相關文章
相關標籤/搜索