Spring 事務(wù)屬性分析

Spring 事務(wù)屬性分析

事務(wù)管理對(duì)于企業(yè)應(yīng)用而言至關(guān)重要肩碟。它保證了用戶的每一次操作都是可靠的强窖,即便出現(xiàn)了異常的訪問情況,也不至于破壞后臺(tái)數(shù)據(jù)的完整性削祈。就像銀行的自助取款機(jī)翅溺,通常都能正常為客戶服務(wù),但是也難免遇到操作過程中機(jī)器突然出故障的情況髓抑,此時(shí)咙崎,事務(wù)就必須確保出故障前對(duì)賬戶的操作不生效,就像用戶剛才完全沒有使用過取款機(jī)一樣吨拍,以保證用戶和銀行的利益都不受損失褪猛。
在 Spring 中,事務(wù)是通過 TransactionDefinition 接口來定義的羹饰。該接口包含與事務(wù)屬性有關(guān)的方法伊滋。具體如清單1所示:
清單1. TransactionDefinition 接口中定義的主要方法

public interface TransactionDefinition{
    int getIsolationLevel();
    int getPropagationBehavior();
    int getTimeout();
    boolean isReadOnly();
}

也許你會(huì)奇怪碳却,為什么接口只提供了獲取屬性的方法,而沒有提供相關(guān)設(shè)置屬性的方法笑旺。其實(shí)道理很簡單昼浦,事務(wù)屬性的設(shè)置完全是程序員控制的,因此程序員可以自定義任何設(shè)置屬性的方法筒主,而且保存屬性的字段也沒有任何要求关噪。唯一的要求的是,Spring 進(jìn)行事務(wù)操作的時(shí)候乌妙,通過調(diào)用以上接口提供的方法必須能夠返回事務(wù)相關(guān)的屬性取值使兔。

事務(wù)隔離級(jí)別

隔離級(jí)別是指若干個(gè)并發(fā)的事務(wù)之間的隔離程度。TransactionDefinition 接口中定義了五個(gè)表示隔離級(jí)別的常量:
TransactionDefinition.ISOLATION_DEFAULT:這是默認(rèn)值冠胯,表示使用底層數(shù)據(jù)庫的默認(rèn)隔離級(jí)別火诸。對(duì)大部分?jǐn)?shù)據(jù)庫而言锦针,通常這值就是TransactionDefinition.ISOLATION_READ_COMMITTED荠察。
TransactionDefinition.ISOLATION_READ_UNCOMMITTED:該隔離級(jí)別表示一個(gè)事務(wù)可以讀取另一個(gè)事務(wù)修改但還沒有提交的數(shù)據(jù)。該級(jí)別不能防止臟讀和不可重復(fù)讀奈搜,因此很少使用該隔離級(jí)別悉盆。
TransactionDefinition.ISOLATION_READ_COMMITTED:該隔離級(jí)別表示一個(gè)事務(wù)只能讀取另一個(gè)事務(wù)已經(jīng)提交的數(shù)據(jù)。該級(jí)別可以防止臟讀馋吗,這也是大多數(shù)情況下的推薦值焕盟。
TransactionDefinition.ISOLATION_REPEATABLE_READ:該隔離級(jí)別表示一個(gè)事務(wù)在整個(gè)過程中可以多次重復(fù)執(zhí)行某個(gè)查詢,并且每次返回的記錄都相同宏粤。即使在多次查詢之間有新增的數(shù)據(jù)滿足該查詢脚翘,這些新增的記錄也會(huì)被忽略。該級(jí)別可以防止臟讀和不可重復(fù)讀绍哎。
TransactionDefinition.ISOLATION_SERIALIZABLE:所有的事務(wù)依次逐個(gè)執(zhí)行来农,這樣事務(wù)之間就完全不可能產(chǎn)生干擾,也就是說崇堰,該級(jí)別可以防止臟讀沃于、不可重復(fù)讀以及幻讀。但是這將嚴(yán)重影響程序的性能海诲。通常情況下也不會(huì)用到該級(jí)別繁莹。

事務(wù)傳播行為

所謂事務(wù)的傳播行為是指,如果在開始當(dāng)前事務(wù)之前特幔,一個(gè)事務(wù)上下文已經(jīng)存在咨演,此時(shí)有若干選項(xiàng)可以指定一個(gè)事務(wù)性方法的執(zhí)行行為。在TransactionDefinition定義中包括了如下幾個(gè)表示傳播行為的常量:
TransactionDefinition.PROPAGATION_REQUIRED:如果當(dāng)前存在事務(wù)蚯斯,則加入該事務(wù)薄风;如果當(dāng)前沒有事務(wù)零院,則創(chuàng)建一個(gè)新的事務(wù)。
TransactionDefinition.PROPAGATION_REQUIRES_NEW:創(chuàng)建一個(gè)新的事務(wù)村刨,如果當(dāng)前存在事務(wù)告抄,則把當(dāng)前事務(wù)掛起。
TransactionDefinition.PROPAGATION_SUPPORTS:如果當(dāng)前存在事務(wù)嵌牺,則加入該事務(wù)打洼;如果當(dāng)前沒有事務(wù),則以非事務(wù)的方式繼續(xù)運(yùn)行逆粹。
TransactionDefinition.PROPAGATION_NOT_SUPPORTED:以非事務(wù)方式運(yùn)行募疮,如果當(dāng)前存在事務(wù),則把當(dāng)前事務(wù)掛起僻弹。
TransactionDefinition.PROPAGATION_NEVER:以非事務(wù)方式運(yùn)行阿浓,如果當(dāng)前存在事務(wù),則拋出異常蹋绽。
TransactionDefinition.PROPAGATION_MANDATORY:如果當(dāng)前存在事務(wù)芭毙,則加入該事務(wù);如果當(dāng)前沒有事務(wù)卸耘,則拋出異常退敦。
TransactionDefinition.PROPAGATION_NESTED:如果當(dāng)前存在事務(wù),則創(chuàng)建一個(gè)事務(wù)作為當(dāng)前事務(wù)的嵌套事務(wù)來運(yùn)行蚣抗;如果當(dāng)前沒有事務(wù)侈百,則該取值等價(jià)于TransactionDefinition.PROPAGATION_REQUIRED。

這里需要指出的是翰铡,前面的六種事務(wù)傳播行為是 Spring 從 EJB 中引入的钝域,他們共享相同的概念。而PROPAGATION_NESTED是 Spring 所特有的锭魔。以 PROPAGATION_NESTED 啟動(dòng)的事務(wù)內(nèi)嵌于外部事務(wù)中(如果存在外部事務(wù)的話)例证,此時(shí),內(nèi)嵌事務(wù)并不是一個(gè)獨(dú)立的事務(wù)赂毯,它依賴于外部事務(wù)的存在战虏,只有通過外部的事務(wù)提交,才能引起內(nèi)部事務(wù)的提交党涕,嵌套的子事務(wù)不能單獨(dú)提交烦感。如果熟悉 JDBC 中的保存點(diǎn)(SavePoint)的概念,那嵌套事務(wù)就很容易理解了膛堤,其實(shí)嵌套的子事務(wù)就是保存點(diǎn)的一個(gè)應(yīng)用手趣,一個(gè)事務(wù)中可以包括多個(gè)保存點(diǎn),每一個(gè)嵌套子事務(wù)。另外绿渣,外部事務(wù)的回滾也會(huì)導(dǎo)致嵌套子事務(wù)的回滾朝群。

事務(wù)超時(shí)

所謂事務(wù)超時(shí),就是指一個(gè)事務(wù)所允許執(zhí)行的最長時(shí)間中符,如果超過該時(shí)間限制但事務(wù)還沒有完成姜胖,則自動(dòng)回滾事務(wù)。在 TransactionDefinition 中以 int 的值來表示超時(shí)時(shí)間淀散,其單位是秒右莱。

事務(wù)的只讀屬性

事務(wù)的只讀屬性是指,對(duì)事務(wù)性資源進(jìn)行只讀操作或者是讀寫操作档插。所謂事務(wù)性資源就是指那些被事務(wù)管理的資源慢蜓,比如數(shù)據(jù)源、 JMS 資源郭膛,以及自定義的事務(wù)性資源等等晨抡。如果確定只對(duì)事務(wù)性資源進(jìn)行只讀操作,那么我們可以將事務(wù)標(biāo)志為只讀的则剃,以提高事務(wù)處理的性能耘柱。在 TransactionDefinition 中以 boolean 類型來表示該事務(wù)是否只讀。

事務(wù)的回滾規(guī)則

通常情況下忍级,如果在事務(wù)中拋出了未檢查異常(繼承自 RuntimeException 的異常)帆谍,則默認(rèn)將回滾事務(wù)。如果沒有拋出任何異常轴咱,或者拋出了已檢查異常,則仍然提交事務(wù)烈涮。這通常也是大多數(shù)開發(fā)者希望的處理方式朴肺,也是 EJB 中的默認(rèn)處理方式。但是坚洽,我們可以根據(jù)需要人為控制事務(wù)在拋出某些未檢查異常時(shí)任然提交事務(wù)戈稿,或者在拋出某些已檢查異常時(shí)回滾事務(wù)。

Spring 事務(wù)管理 API 分析

Spring 框架中讶舰,涉及到事務(wù)管理的 API 大約有100個(gè)左右鞍盗,其中最重要的有三個(gè):TransactionDefinition、PlatformTransactionManager跳昼、TransactionStatus般甲。所謂事務(wù)管理,其實(shí)就是“按照給定的事務(wù)規(guī)則來執(zhí)行提交或者回滾操作”鹅颊》蟠妫“給定的事務(wù)規(guī)則”就是用 TransactionDefinition 表示的,“按照……來執(zhí)行提交或者回滾操作”便是用 PlatformTransactionManager 來表示堪伍,而 TransactionStatus 用于表示一個(gè)運(yùn)行著的事務(wù)的狀態(tài)锚烦。打一個(gè)不恰當(dāng)?shù)谋扔髅倜觯琓ransactionDefinition 與 TransactionStatus 的關(guān)系就像程序和進(jìn)程的關(guān)系。

TransactionDef...

該接口在前面已經(jīng)介紹過涮俄,它用于定義一個(gè)事務(wù)蛉拙。它包含了事務(wù)的靜態(tài)屬性,比如:事務(wù)傳播行為彻亲、超時(shí)時(shí)間等等刘离。Spring 為我們提供了一個(gè)默認(rèn)的實(shí)現(xiàn)類:DefaultTransactionDefinition,該類適用于大多數(shù)情況睹栖。如果該類不能滿足需求硫惕,可以通過實(shí)現(xiàn) TransactionDefinition 接口來實(shí)現(xiàn)自己的事務(wù)定義。

PlatformTrans...

PlatformTransactionManager 用于執(zhí)行具體的事務(wù)操作野来。接口定義如清單2所示:
清單2. PlatformTransactionManager 接口中定義的主要方法

Public interface PlatformTransactionManager{
    TransactionStatus getTransaction(TransactionDefinition definition)
    throws TransactionException;
    void commit(TransactionStatus status)throws TransactionException;
    void rollback(TransactionStatus status)throws TransactionException;
}

根據(jù)底層所使用的不同的持久化 API 或框架恼除,PlatformTransactionManager 的主要實(shí)現(xiàn)類大致如下:
DataSourceTransactionManager:適用于使用JDBC和iBatis進(jìn)行數(shù)據(jù)持久化操作的情況。
HibernateTransactionManager:適用于使用Hibernate進(jìn)行數(shù)據(jù)持久化操作的情況曼氛。
JpaTransactionManager:適用于使用JPA進(jìn)行數(shù)據(jù)持久化操作的情況豁辉。
另外還有JtaTransactionManager 、JdoTransactionManager舀患、JmsTransactionManager等等徽级。
如果我們使用JTA進(jìn)行事務(wù)管理,我們可以通過 JNDI 和 Spring 的 JtaTransactionManager 來獲取一個(gè)容器管理的 DataSource聊浅。JtaTransactionManager 不需要知道 DataSource 和其他特定的資源餐抢,因?yàn)樗鼘⑹褂萌萜魈峁┑娜质聞?wù)管理。而對(duì)于其他事務(wù)管理器低匙,比如DataSourceTransactionManager旷痕,在定義時(shí)需要提供底層的數(shù)據(jù)源作為其屬性,也就是 DataSource顽冶。與 HibernateTransactionManager 對(duì)應(yīng)的是 SessionFactory欺抗,與 JpaTransactionManager 對(duì)應(yīng)的是 EntityManagerFactory 等等。

TransactionStatus

PlatformTransactionManager.getTransaction(…) 方法返回一個(gè) TransactionStatus 對(duì)象强重。返回的TransactionStatus 對(duì)象可能代表一個(gè)新的或已經(jīng)存在的事務(wù)(如果在當(dāng)前調(diào)用堆棧有一個(gè)符合條件的事務(wù))绞呈。TransactionStatus 接口提供了一個(gè)簡單的控制事務(wù)執(zhí)行和查詢事務(wù)狀態(tài)的方法。該接口定義如清單3所示:
清單3. TransactionStatus 接口中定義的主要方法

public  interface TransactionStatus{
   boolean isNewTransaction();
   void setRollbackOnly();
   boolean isRollbackOnly();
}

編程式事務(wù)管理

Spring 的編程式事務(wù)管理概述

在 Spring 出現(xiàn)以前间景,編程式事務(wù)管理對(duì)基于 POJO 的應(yīng)用來說是唯一選擇佃声。用過 Hibernate 的人都知道,我們需要在代碼中顯式調(diào)用beginTransaction()拱燃、commit()秉溉、rollback()等事務(wù)管理相關(guān)的方法,這就是編程式事務(wù)管理。通過 Spring 提供的事務(wù)管理 API召嘶,我們可以在代碼中靈活控制事務(wù)的執(zhí)行父晶。在底層,Spring 仍然將事務(wù)操作委托給底層的持久化框架來執(zhí)行弄跌。

基于底層 API 的編程式事務(wù)管理

根據(jù)PlatformTransactionManager甲喝、TransactionDefinition 和 TransactionStatus 三個(gè)核心接口,我們完全可以通過編程的方式來進(jìn)行事務(wù)管理铛只。示例代碼如清單4所示:
清單4. 基于底層 API 的事務(wù)管理示例代碼

public class BankServiceImpl implements BankService {
    private BankDao bankDao;
    private TransactionDefinition txDefinition;
    private PlatformTransactionManager txManager;
    ......
    public boolean transfer(Long fromId埠胖, Long toId, double amount) {
        TransactionStatus txStatus = txManager.getTransaction(txDefinition);
        boolean result = false;
        try {
            result = bankDao.transfer(fromId淳玩, toId直撤, amount);
            txManager.commit(txStatus);
        } catch (Exception e) {
            result = false;
            txManager.rollback(txStatus);
            System.out.println("Transfer Error!");
        }
        return result;
    }
}

相應(yīng)的配置文件如清單5所示:
清單5. 基于底層API的事務(wù)管理示例配置文件

<bean id="bankService" class="footmark.spring.core.tx.programmatic.origin.BankServiceImpl">
    <property name="bankDao" ref="bankDao"/>
    <property name="txManager" ref="transactionManager"/>
    <property name="txDefinition">
    <bean class="org.springframework.transaction.support.DefaultTransactionDefinition">
        <property name="propagationBehaviorName" value="PROPAGATION_REQUIRED"/>
    </bean>
    </property>
</bean>

如上所示,我們?cè)陬愔性黾恿藘蓚€(gè)屬性:一個(gè)是 TransactionDefinition 類型的屬性蜕着,它用于定義一個(gè)事務(wù)谋竖;另一個(gè)是 PlatformTransactionManager 類型的屬性,用于執(zhí)行事務(wù)管理操作承匣。
如果方法需要實(shí)施事務(wù)管理蓖乘,我們首先需要在方法開始執(zhí)行前啟動(dòng)一個(gè)事務(wù),調(diào)用PlatformTransactionManager.getTransaction(...) 方法便可啟動(dòng)一個(gè)事務(wù)韧骗。創(chuàng)建并啟動(dòng)了事務(wù)之后嘉抒,便可以開始編寫業(yè)務(wù)邏輯代碼,然后在適當(dāng)?shù)牡胤綀?zhí)行事務(wù)的提交或者回滾袍暴。

基于 TransactionTemplate 的編程式事務(wù)管理

通過前面的示例可以發(fā)現(xiàn)些侍,這種事務(wù)管理方式很容易理解,但令人頭疼的是容诬,事務(wù)管理的代碼散落在業(yè)務(wù)邏輯代碼中娩梨,破壞了原有代碼的條理性,并且每一個(gè)業(yè)務(wù)方法都包含了類似的啟動(dòng)事務(wù)览徒、提交/回滾事務(wù)的樣板代碼。幸好颂龙,Spring 也意識(shí)到了這些习蓬,并提供了簡化的方法,這就是 Spring 在數(shù)據(jù)訪問層非常常見的模板回調(diào)模式措嵌。如清單6所示:
清單6. 基于 TransactionTemplate 的事務(wù)管理示例代碼

public class BankServiceImpl implements BankService {
    private BankDao bankDao;
    private TransactionTemplate transactionTemplate;
    ......
    public boolean transfer(final Long fromId官研, final Long toId复局, final double amount) {
        return (Boolean) transactionTemplate.execute(new TransactionCallback(){
            public Object doInTransaction(TransactionStatus status) {
                Object result;
                try {
                    result = bankDao.transfer(fromId, toId, amount);
                } catch (Exception e) {
                    status.setRollbackOnly();
                    result = false;
                    System.out.println("Transfer Error!");
                }
                return result;
            }
        });
    }
}

相應(yīng)的XML配置如下:
清單 7. 基于 TransactionTemplate 的事務(wù)管理示例配置文件

<bean id="bankService" class="footmark.spring.core.tx.programmatic.template.BankServiceImpl">
    <property name="bankDao" ref="bankDao"/>
    <property name="transactionTemplate" ref="transactionTemplate"/>
</bean>

TransactionTemplate 的 execute() 方法有一個(gè) TransactionCallback 類型的參數(shù)序调,該接口中定義了一個(gè) doInTransaction() 方法睛约,通常我們以匿名內(nèi)部類的方式實(shí)現(xiàn) TransactionCallback 接口,并在其 doInTransaction() 方法中書寫業(yè)務(wù)邏輯代碼。這里可以使用默認(rèn)的事務(wù)提交和回滾規(guī)則探孝,這樣在業(yè)務(wù)代碼中就不需要顯式調(diào)用任何事務(wù)管理的 API。doInTransaction() 方法有一個(gè)TransactionStatus 類型的參數(shù)誉裆,我們可以在方法的任何位置調(diào)用該參數(shù)的 setRollbackOnly() 方法將事務(wù)標(biāo)識(shí)為回滾的顿颅,以執(zhí)行事務(wù)回滾。
根據(jù)默認(rèn)規(guī)則足丢,如果在執(zhí)行回調(diào)方法的過程中拋出了未檢查異常粱腻,或者顯式調(diào)用了TransacationStatus.setRollbackOnly() 方法,則回滾事務(wù)斩跌;如果事務(wù)執(zhí)行完成或者拋出了 checked 類型的異常绍些,則提交事務(wù)。
TransactionCallback 接口有一個(gè)子接口 TransactionCallbackWithoutResult耀鸦,該接口中定義了一個(gè) doInTransactionWithoutResult() 方法柬批,TransactionCallbackWithoutResult 接口主要用于事務(wù)過程中不需要返回值的情況。當(dāng)然揭糕,對(duì)于不需要返回值的情況萝快,我們?nèi)匀豢梢允褂?TransactionCallback 接口,并在方法中返回任意值即可著角。

聲明式事務(wù)管理

Spring 的聲明式事務(wù)管理概述

Spring 的聲明式事務(wù)管理在底層是建立在 AOP 的基礎(chǔ)之上的揪漩。其本質(zhì)是對(duì)方法前后進(jìn)行攔截,然后在目標(biāo)方法開始之前創(chuàng)建或者加入一個(gè)事務(wù)吏口,在執(zhí)行完目標(biāo)方法之后根據(jù)執(zhí)行情況提交或者回滾事務(wù)奄容。

聲明式事務(wù)最大的優(yōu)點(diǎn)就是不需要通過編程的方式管理事務(wù),這樣就不需要在業(yè)務(wù)邏輯代碼中摻雜事務(wù)管理的代碼产徊,只需在配置文件中做相關(guān)的事務(wù)規(guī)則聲明(或通過等價(jià)的基于標(biāo)注的方式)昂勒,便可以將事務(wù)規(guī)則應(yīng)用到業(yè)務(wù)邏輯中。因?yàn)槭聞?wù)管理本身就是一個(gè)典型的橫切邏輯舟铜,正是 AOP 的用武之地戈盈。Spring 開發(fā)團(tuán)隊(duì)也意識(shí)到了這一點(diǎn),為聲明式事務(wù)提供了簡單而強(qiáng)大的支持谆刨。

聲明式事務(wù)管理曾經(jīng)是 EJB 引以為傲的一個(gè)亮點(diǎn)塘娶,如今 Spring 讓 POJO 在事務(wù)管理方面也擁有了和 EJB 一樣的待遇,讓開發(fā)人員在 EJB 容器之外也用上了強(qiáng)大的聲明式事務(wù)管理功能痊夭,這主要得益于 Spring 依賴注入容器和 Spring AOP 的支持刁岸。依賴注入容器為聲明式事務(wù)管理提供了基礎(chǔ)設(shè)施,使得 Bean 對(duì)于 Spring 框架而言是可管理的她我;而 Spring AOP 則是聲明式事務(wù)管理的直接實(shí)現(xiàn)者虹曙,這一點(diǎn)通過清單8可以看出來迫横。

通常情況下,筆者強(qiáng)烈建議在開發(fā)中使用聲明式事務(wù)酝碳,不僅因?yàn)槠浜唵畏猓饕且驗(yàn)檫@樣使得純業(yè)務(wù)代碼不被污染,極大方便后期的代碼維護(hù)击敌。

和編程式事務(wù)相比介返,聲明式事務(wù)唯一不足地方是,后者的最細(xì)粒度只能作用到方法級(jí)別沃斤,無法做到像編程式事務(wù)那樣可以作用到代碼塊級(jí)別圣蝎。但是即便有這樣的需求,也存在很多變通的方法衡瓶,比如徘公,可以將需要進(jìn)行事務(wù)管理的代碼塊獨(dú)立為方法等等。

下面就來看看 Spring 為我們提供的聲明式事務(wù)管理功能哮针。

基于 TransactionInter... 的聲明式事務(wù)管理

最初关面,Spring 提供了 TransactionInterceptor 類來實(shí)施聲明式事務(wù)管理功能。先看清單8的配置文件:
清單 8. 基于 TransactionInterceptor 的事務(wù)管理示例配置文件

<beans...>
......
    <bean id="transactionInterceptor" class="org.springframework.transaction.interceptor.TransactionInterceptor">
        <property name="transactionManager" ref="transactionManager"/>
        <property name="transactionAttributes">
        <props>
            <prop key="transfer">PROPAGATION_REQUIRED</prop>
        </props>
        </property>
    </bean>
    
    <bean id="bankServiceTarget" class="footmark.spring.core.tx.declare.origin.BankServiceImpl">
        <property name="bankDao" ref="bankDao"/>
    </bean>
    
    <bean id="bankService" class="org.springframework.aop.framework.ProxyFactoryBean">
        <property name="target" ref="bankServiceTarget"/>
        <property name="interceptorNames">
            <list><idref bean="transactionInterceptor"/></list>
        </property>
    </bean>
......
</beans>

首先十厢,我們配置了一個(gè) TransactionInterceptor 來定義相關(guān)的事務(wù)規(guī)則等太,他有兩個(gè)主要的屬性:一個(gè)是 transactionManager,用來指定一個(gè)事務(wù)管理器蛮放,并將具體事務(wù)相關(guān)的操作委托給它缩抡;另一個(gè)是 Properties 類型的 transactionAttributes 屬性,它主要用來定義事務(wù)規(guī)則包颁,該屬性的每一個(gè)鍵值對(duì)中瞻想,鍵指定的是方法名,方法名可以使用通配符娩嚼,而值就表示相應(yīng)方法的所應(yīng)用的事務(wù)屬性蘑险。
指定事務(wù)屬性的取值有較復(fù)雜的規(guī)則,這在 Spring 中算得上是一件讓人頭疼的事岳悟。具體的書寫規(guī)則如下:

傳播行為 [佃迄,隔離級(jí)別] [,只讀屬性] [贵少,超時(shí)屬性] [不影響提交的異常] [和屎,導(dǎo)致回滾的異常]
傳播行為是唯一必須設(shè)置的屬性,其他都可以忽略春瞬,Spring為我們提供了合理的默認(rèn)值。
傳播行為的取值必須以“PROPAGATION_”開頭套啤,具體包括:PROPAGATION_MANDATORY宽气、PROPAGATION_NESTED随常、PROPAGATION_NEVER、PROPAGATION_NOT_SUPPORTED萄涯、PROPAGATION_REQUIRED绪氛、PROPAGATION_REQUIRES_NEW、PROPAGATION_SUPPORTS涝影,共七種取值枣察。
隔離級(jí)別的取值必須以“ISOLATION_”開頭,具體包括:ISOLATION_DEFAULT燃逻、ISOLATION_READ_COMMITTED序目、ISOLATION_READ_UNCOMMITTED、ISOLATION_REPEATABLE_READ伯襟、ISOLATION_SERIALIZABLE猿涨,共五種取值。
如果事務(wù)是只讀的姆怪,那么我們可以指定只讀屬性叛赚,使用“readOnly”指定。否則我們不需要設(shè)置該屬性稽揭。
超時(shí)屬性的取值必須以“TIMEOUT_”開頭俺附,后面跟一個(gè)int類型的值,表示超時(shí)時(shí)間溪掀,單位是秒事镣。
不影響提交的異常是指,即使事務(wù)中拋出了這些類型的異常膨桥,事務(wù)任然正常提交蛮浑。必須在每一個(gè)異常的名字前面加上“+”。異常的名字可以是類名的一部分只嚣。比如“+RuntimeException”沮稚、“+tion”等等。
導(dǎo)致回滾的異常是指册舞,當(dāng)事務(wù)中拋出這些類型的異常時(shí)蕴掏,事務(wù)將回滾。必須在每一個(gè)異常的名字前面加上“-”调鲸。異常的名字可以是類名的全部或者部分盛杰,比如“-RuntimeException”、“-tion”等等藐石。
以下是兩個(gè)示例:

<property name="*Service">
PROPAGATION_REQUIRED即供,ISOLATION_READ_COMMITTED,TIMEOUT_20于微,
+AbcException逗嫡,+DefException青自,-HijException
</property>

以上表達(dá)式表示,針對(duì)所有方法名以 Service 結(jié)尾的方法驱证,使用 PROPAGATION_REQUIRED 事務(wù)傳播行為延窜,事務(wù)的隔離級(jí)別是 ISOLATION_READ_COMMITTED,超時(shí)時(shí)間為20秒抹锄,當(dāng)事務(wù)拋出 AbcException 或者 DefException 類型的異常逆瑞,則仍然提交,當(dāng)拋出 HijException 類型的異常時(shí)必須回滾事務(wù)伙单。這里沒有指定"readOnly"获高,表示事務(wù)不是只讀的。

<property name="test">PROPAGATION_REQUIRED车份,readOnly</property>

以上表達(dá)式表示谋减,針對(duì)所有方法名為 test 的方法,使用 PROPAGATION_REQUIRED 事務(wù)傳播行為扫沼,并且該事務(wù)是只讀的出爹。除此之外,其他的屬性均使用默認(rèn)值缎除。比如严就,隔離級(jí)別和超時(shí)時(shí)間使用底層事務(wù)性資源的默認(rèn)值,并且當(dāng)發(fā)生未檢查異常器罐,則回滾事務(wù)梢为,發(fā)生已檢查異常則仍提交事務(wù)。

配置好了 TransactionInterceptor轰坊,我們還需要配置一個(gè) ProxyFactoryBean 來組裝 target 和advice铸董。這也是典型的 Spring AOP 的做法。通過 ProxyFactoryBean 生成的代理類就是織入了事務(wù)管理邏輯后的目標(biāo)類肴沫。至此粟害,聲明式事務(wù)管理就算是實(shí)現(xiàn)了。我們沒有對(duì)業(yè)務(wù)代碼進(jìn)行任何操作颤芬,所有設(shè)置均在配置文件中完成悲幅,這就是聲明式事務(wù)的最大優(yōu)點(diǎn)。

基于 TransactionProxy... 的聲明式事務(wù)管理

前面的聲明式事務(wù)雖然好站蝠,但是卻存在一個(gè)非常惱人的問題:配置文件太多汰具。我們必須針對(duì)每一個(gè)目標(biāo)對(duì)象配置一個(gè) ProxyFactoryBean;另外菱魔,雖然可以通過父子 Bean 的方式來復(fù)用 TransactionInterceptor 的配置留荔,但是實(shí)際的復(fù)用幾率也不高;這樣澜倦,加上目標(biāo)對(duì)象本身存谎,每一個(gè)業(yè)務(wù)類可能需要對(duì)應(yīng)三個(gè) <bean/> 配置拔疚,隨著業(yè)務(wù)類的增多,配置文件將會(huì)變得越來越龐大既荚,管理配置文件又成了問題。

為了緩解這個(gè)問題栋艳,Spring 為我們提供了 TransactionProxyFactoryBean恰聘,用于將TransactionInterceptor 和 ProxyFactoryBean 的配置合二為一。如清單9所示:
清單9. 基于 TransactionProxyFactoryBean 的事務(wù)管理示例配置文件

<beans......>
    ......
    <bean id="bankServiceTarget" class="footmark.spring.core.tx.declare.classic.BankServiceImpl">
        <property name="bankDao" ref="bankDao"/>
    </bean>
    
    <bean id="bankService" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
        <property name="target" ref="bankServiceTarget"/>
        <property name="transactionManager" ref="transactionManager"/>
        <property name="transactionAttributes">
            <props>
                <prop key="transfer">PROPAGATION_REQUIRED</prop>
            </props>
        </property>
    </bean>
    ......
</beans>

如此一來吸占,配置文件與先前相比簡化了很多晴叨。我們把這種配置方式稱為 Spring 經(jīng)典的聲明式事務(wù)管理。相信在早期使用 Spring 的開發(fā)人員對(duì)這種配置聲明式事務(wù)的方式一定非常熟悉矾屯。

但是兼蕊,顯式為每一個(gè)業(yè)務(wù)類配置一個(gè) TransactionProxyFactoryBean 的做法將使得代碼顯得過于刻板,為此我們可以使用自動(dòng)創(chuàng)建代理的方式來將其簡化件蚕,使用自動(dòng)創(chuàng)建代理是純 AOP 知識(shí)孙技,請(qǐng)讀者參考相關(guān)文檔,不在此贅述排作。

基于 <tx> 命名空間的聲明式事務(wù)管理

前面兩種聲明式事務(wù)配置方式奠定了 Spring 聲明式事務(wù)管理的基石牵啦。在此基礎(chǔ)上,Spring 2.x 引入了 <tx> 命名空間妄痪,結(jié)合使用 <aop> 命名空間哈雏,帶給開發(fā)人員配置聲明式事務(wù)的全新體驗(yàn),配置變得更加簡單和靈活衫生。另外裳瘪,得益于 <aop> 命名空間的切點(diǎn)表達(dá)式支持,聲明式事務(wù)也變得更加強(qiáng)大罪针。
如清單10所示:
清單10. 基于 <tx> 的事務(wù)管理示例配置文件

<beans......>
    ......
    <bean id="bankService" class="footmark.spring.core.tx.declare.namespace.BankServiceImpl">
        <property name="bankDao" ref="bankDao"/>
    </bean>
    
    <tx:advice id="bankAdvice" transaction-manager="transactionManager">
        <tx:attributes>
            <tx:method name="transfer" propagation="REQUIRED"/>
        </tx:attributes>
    </tx:advice>
     
    <aop:config>
        <aop:pointcut id="bankPointcut" expression="execution(* *.transfer(..))"/>
        <aop:advisor advice-ref="bankAdvice" pointcut-ref="bankPointcut"/>
    </aop:config>
    ......
</beans>

如果默認(rèn)的事務(wù)屬性就能滿足要求彭羹,那么代碼簡化為如清單 11 所示:
清單 11. 簡化后的基于 <tx> 的事務(wù)管理示例配置文件

<beans......>
    ......
    <bean id="bankService" class="footmark.spring.core.tx.declare.namespace.BankServiceImpl">
        <property name="bankDao" ref="bankDao"/>
    </bean>
    
    <tx:advice id="bankAdvice" transaction-manager="transactionManager">
    <aop:config>
        <aop:pointcut id="bankPointcut" expression="execution(**.transfer(..))"/>
        <aop:advisor advice-ref="bankAdvice" pointcut-ref="bankPointcut"/>
    </aop:config>
    ......
</beans>

由于使用了切點(diǎn)表達(dá)式,我們就不需要針對(duì)每一個(gè)業(yè)務(wù)類創(chuàng)建一個(gè)代理對(duì)象了站故。另外皆怕,如果配置的事務(wù)管理器 Bean 的名字取值為“transactionManager”,則我們可以省略 <tx:advice> 的 transaction-manager 屬性西篓,因?yàn)樵搶傩缘哪J(rèn)值即為“transactionManager”愈腾。

基于 @Transactional 的聲明式事務(wù)管理

除了基于命名空間的事務(wù)配置方式,Spring 2.x 還引入了基于 Annotation 的方式岂津,具體主要涉及@Transactional 標(biāo)注虱黄。@Transactional 可以作用于接口、接口方法吮成、類以及類方法上橱乱。當(dāng)作用于類上時(shí)辜梳,該類的所有 public 方法將都具有該類型的事務(wù)屬性,同時(shí)泳叠,我們也可以在方法級(jí)別使用該標(biāo)注來覆蓋類級(jí)別的定義作瞄。如清單12所示:
清單12. 基于 @Transactional 的事務(wù)管理示例配置文件

@Transactional(propagation = Propagation.REQUIRED)
public boolean transfer(Long fromId, Long toId危纫, double amount) {
    return bankDao.transfer(fromId宗挥, toId, amount);
}

Spring 使用 BeanPostProcessor 來處理 Bean 中的標(biāo)注种蝶,因此我們需要在配置文件中作如下聲明來激活該后處理 Bean契耿,如清單13所示:
清單13. 啟用后處理Bean的配置

<tx:annotation-driven transaction-manager="transactionManager"/>

與前面相似,transaction-manager 屬性的默認(rèn)值是 transactionManager螃征,如果事務(wù)管理器 Bean 的名字即為該值搪桂,則可以省略該屬性。

雖然 @Transactional 注解可以作用于接口盯滚、接口方法踢械、類以及類方法上,但是 Spring 小組建議不要在接口或者接口方法上使用該注解淌山,因?yàn)檫@只有在使用基于接口的代理時(shí)它才會(huì)生效裸燎。另外, @Transactional 注解應(yīng)該只被應(yīng)用到 public 方法上泼疑,這是由 Spring AOP 的本質(zhì)決定的德绿。如果你在 protected、private 或者默認(rèn)可見性的方法上使用 @Transactional 注解退渗,這將被忽略移稳,也不會(huì)拋出任何異常。

基于 <tx> 命名空間和基于 @Transactional 的事務(wù)聲明方式各有優(yōu)缺點(diǎn)会油「隽唬基于 <tx> 的方式,其優(yōu)點(diǎn)是與切點(diǎn)表達(dá)式結(jié)合翻翩,功能強(qiáng)大都许。利用切點(diǎn)表達(dá)式,一個(gè)配置可以匹配多個(gè)方法嫂冻,而基于 @Transactional 的方式必須在每一個(gè)需要使用事務(wù)的方法或者類上用 @Transactional 標(biāo)注胶征,盡管可能大多數(shù)事務(wù)的規(guī)則是一致的,但是對(duì) @Transactional 而言桨仿,也無法重用睛低,必須逐個(gè)指定。另一方面,基于 @Transactional 的方式使用起來非常簡單明了钱雷,沒有學(xué)習(xí)成本骂铁。開發(fā)人員可以根據(jù)需要,任選其中一種使用罩抗,甚至也可以根據(jù)需要混合使用這兩種方式拉庵。

如果不是對(duì)遺留代碼進(jìn)行維護(hù),則不建議再使用基于 TransactionInterceptor 以及基于TransactionProxyFactoryBean 的聲明式事務(wù)管理方式澄暮,但是名段,學(xué)習(xí)這兩種方式非常有利于對(duì)底層實(shí)現(xiàn)的理解。

雖然上面共列舉了四種聲明式事務(wù)管理方式泣懊,但是這樣的劃分只是為了便于理解,其實(shí)后臺(tái)的實(shí)現(xiàn)方式是一樣的麻惶,只是用戶使用的方式不同而已馍刮。

結(jié)束語

本教程的知識(shí)點(diǎn)大致總結(jié)如下:
基于 TransactionDefinition、PlatformTransactionManager窃蹋、TransactionStatus 編程式事務(wù)管理是 Spring 提供的最原始的方式卡啰,通常我們不會(huì)這么寫,但是了解這種方式對(duì)理解 Spring 事務(wù)管理的本質(zhì)有很大作用警没。

基于 TransactionTemplate 的編程式事務(wù)管理是對(duì)上一種方式的封裝匈辱,使得編碼更簡單、清晰杀迹。

基于 TransactionInterceptor 的聲明式事務(wù)是 Spring 聲明式事務(wù)的基礎(chǔ)亡脸,通常也不建議使用這種方式,但是與前面一樣树酪,了解這種方式對(duì)理解 Spring 聲明式事務(wù)有很大作用浅碾。

基于 TransactionProxyFactoryBean 的聲明式事務(wù)是上中方式的改進(jìn)版本,簡化的配置文件的書寫续语,這是 Spring 早期推薦的聲明式事務(wù)管理方式垂谢,但是在 Spring 2.0 中已經(jīng)不推薦了。

基于 <tx> 和 <aop> 命名空間的聲明式事務(wù)管理是目前推薦的方式疮茄,其最大特點(diǎn)是與 Spring AOP 結(jié)合緊密滥朱,可以充分利用切點(diǎn)表達(dá)式的強(qiáng)大支持,使得管理事務(wù)更加靈活力试。

基于 @Transactional 的方式將聲明式事務(wù)管理簡化到了極致徙邻。開發(fā)人員只需在配置文件中加上一行啟用相關(guān)后處理 Bean 的配置,然后在需要實(shí)施事務(wù)管理的方法或者類上使用 @Transactional 指定事務(wù)規(guī)則即可實(shí)現(xiàn)事務(wù)管理懂版,而且功能也不必其他方式遜色鹃栽。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子民鼓,更是在濱河造成了極大的恐慌薇芝,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,968評(píng)論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件丰嘉,死亡現(xiàn)場離奇詭異夯到,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)饮亏,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,601評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門耍贾,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人路幸,你說我怎么就攤上這事荐开。” “怎么了简肴?”我有些...
    開封第一講書人閱讀 153,220評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵晃听,是天一觀的道長。 經(jīng)常有香客問我砰识,道長能扒,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,416評(píng)論 1 279
  • 正文 為了忘掉前任辫狼,我火速辦了婚禮初斑,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘膨处。我一直安慰自己见秤,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,425評(píng)論 5 374
  • 文/花漫 我一把揭開白布灵迫。 她就那樣靜靜地躺著秦叛,像睡著了一般。 火紅的嫁衣襯著肌膚如雪瀑粥。 梳的紋絲不亂的頭發(fā)上挣跋,一...
    開封第一講書人閱讀 49,144評(píng)論 1 285
  • 那天,我揣著相機(jī)與錄音狞换,去河邊找鬼避咆。 笑死,一個(gè)胖子當(dāng)著我的面吹牛修噪,可吹牛的內(nèi)容都是我干的查库。 我是一名探鬼主播,決...
    沈念sama閱讀 38,432評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼黄琼,長吁一口氣:“原來是場噩夢啊……” “哼樊销!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,088評(píng)論 0 261
  • 序言:老撾萬榮一對(duì)情侶失蹤围苫,失蹤者是張志新(化名)和其女友劉穎裤园,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體剂府,經(jīng)...
    沈念sama閱讀 43,586評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡拧揽,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,028評(píng)論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了腺占。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片淤袜。...
    茶點(diǎn)故事閱讀 38,137評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖衰伯,靈堂內(nèi)的尸體忽然破棺而出铡羡,到底是詐尸還是另有隱情,我是刑警寧澤意鲸,帶...
    沈念sama閱讀 33,783評(píng)論 4 324
  • 正文 年R本政府宣布蓖墅,位于F島的核電站,受9級(jí)特大地震影響临扮,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜教翩,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,343評(píng)論 3 307
  • 文/蒙蒙 一杆勇、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧饱亿,春花似錦蚜退、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,333評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至配猫,卻和暖如春幅恋,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背泵肄。 一陣腳步聲響...
    開封第一講書人閱讀 31,559評(píng)論 1 262
  • 我被黑心中介騙來泰國打工捆交, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人腐巢。 一個(gè)月前我還...
    沈念sama閱讀 45,595評(píng)論 2 355
  • 正文 我出身青樓品追,卻偏偏與公主長得像,于是被迫代替她去往敵國和親冯丙。 傳聞我的和親對(duì)象是個(gè)殘疾皇子肉瓦,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,901評(píng)論 2 345