1. 程式人生 > >Spring事務原理一探

Spring事務原理一探

Spring事務原理一探

概括來講,事務是一個由有限操作集合組成的邏輯單元。事務操作包含兩個目的,資料一致以及操作隔離。資料一致是指事務提交時保證事務內的所有操作都成功完成,並且更改永久生效;事務回滾時,保證能夠恢復到事務執行之前的狀態。操作隔離則是指多個同時執行的事務之間應該相互獨立,互不影響。

事務是一個比較廣泛的概念,事務管理資源除了我們熟知的資料庫外,還可以包含訊息佇列、檔案系統等。當然,一般來說,我們說的事務單指“資料庫事務”。接下來我們會以MySQL資料庫、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的幻讀問題

傳播機制

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增強

動態代理是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方法中,通過呼叫父類TransactionAspectSupport的invokeWithinTransaction方法進行事務處理,該方法支援宣告式事務和程式設計式事務。

// 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;
    ...

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事務的實現原理和處理流程有了一定的瞭解。