Spring 事務(wù)管理

1 什么是事務(wù)


生活中關(guān)于事務(wù)有一個常見的場景,即銀行用戶轉(zhuǎn)賬盒揉。簡單的講前计,轉(zhuǎn)賬可以分為下面 2 個步驟:

  1. 查看用戶 A 的賬戶是否有足夠的余額,我們假設(shè)轉(zhuǎn)賬 1000 元蛔琅,如果有則從 A 賬戶中扣除 1000 元;
  2. 將用戶 B 的賬戶余額增加 1000 元峻呛。

很明顯罗售,上面的 2 個步驟必須同時成功或者同時失敗,否則將可能出現(xiàn)下面兩種情況:

  1. 用戶 A 扣了錢但用戶 B 沒有收到錢钩述,這樣用戶 A 就損失了 1000 元寨躁;
  2. 用戶 B 的賬戶余額增加了 1000 元,但是用戶A的賬戶余額并沒有減少牙勘,這樣銀行就損失了 1000 元职恳。

所以,只有當(dāng)兩個步驟同時失敗或同時成功谜悟,才是皆大歡喜的結(jié)果话肖。事務(wù)就是用來解決類似問題的。事務(wù)是一系列的動作葡幸,它們綜合在一起才是一個完整的工作單元最筒,這些動作必須全部完成,如果有一個失敗的話蔚叨,那么事務(wù)就會回滾到最開始的狀態(tài)床蜘。

事務(wù)有 4 個特性,為了方便記憶可以將其簡稱為 ACID:

  • 原子性(Atomicity):事務(wù)是一個原子操作蔑水,由一系列動作組成邢锯。事務(wù)的原子性確保動作要么全部完成,要么完全不起作用搀别。
  • 一致性(Consistency):一旦事務(wù)完成(不管成功還是失數で妗),系統(tǒng)必須確保它所建模的業(yè)務(wù)處于一致的狀態(tài)歇父,而不會是部分完成部分失敗蒂培。在現(xiàn)實中的數(shù)據(jù)不應(yīng)該被破壞。
  • 隔離性(Isolation):可能有許多事務(wù)會同時處理相同的數(shù)據(jù)榜苫,因此每個事務(wù)都應(yīng)該與其他事務(wù)隔離開來护戳,防止數(shù)據(jù)損壞。
  • 持久性(Durability):一旦事務(wù)完成垂睬,無論發(fā)生什么系統(tǒng)錯誤媳荒,它的結(jié)果都不應(yīng)該受到影響抗悍,這樣就能從任何系統(tǒng)崩潰中恢復(fù)過來。通常情況下钳枕,事務(wù)的結(jié)果被寫到持久化存儲器中缴渊。

2 Spring 事務(wù)管理核心接口


事務(wù)管理核心接口(圖片來源為網(wǎng)絡(luò))

Spring 事務(wù)管理的核心接口主要有 3 個:

  • PlatformTransactionManager
  • TransactionDefinition
  • TransactionStatus

它們之間的關(guān)系是:TransactionDefinition 包含了事務(wù)的屬性,PlatformTransactionManager 的 getTransaction 方法將使用 TransactionDefinition 作為參數(shù)開始一個事務(wù)并返回事務(wù)的狀態(tài)即 TransactionStatus鱼炒。而不同的平臺又各自實現(xiàn)了 PlatformTransactionManager 來進(jìn)行平臺相關(guān)的事務(wù)處理疟暖。

PlatformTransactionManager

事務(wù)管理器接口定義如下:

public interface PlatformTransactionManager()...{  
    // 由TransactionDefinition開啟一個事務(wù),得到TransactionStatus對象
    TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException; 
    // 提交
    Void commit(TransactionStatus status) throws TransactionException;  
    // 回滾
    Void rollback(TransactionStatus status) throws TransactionException;  
} 

可以看到田柔,其實 spring 事務(wù)的核心思想還是很簡單的,TransactionDefinition 說明事務(wù)的屬性骨望,PlatformTransactionManager 進(jìn)行事務(wù)的管理硬爆,而 TransactionStatus 代表某一個具體的事務(wù)。Spring 也為不同的平臺提供了相應(yīng)的 PlatformTransactionManager 實現(xiàn):

  • JDBC 事務(wù):DataSourceTransactionManager 通過 Java.sql.Connection 來管理事務(wù)擎鸠,調(diào)用 Java.sql.Connection 的commit() 方法來提交事務(wù)缀磕,rollback() 方法來回滾事務(wù);

  • Hibernate 事務(wù):HibernateTransactionManager 將事務(wù)管理的職責(zé)委托給org.hibernate.Transaction對象劣光,而后者是從Hibernate Session 中獲取到的袜蚕。當(dāng)事務(wù)成功完成時,HibernateTransactionManager 將會調(diào)用T ransaction 對象的 commit() 方法绢涡,反之牲剃,將會調(diào)用 rollback() 方法;

  • Jpa 事務(wù): 使用 JpaTransactionManager 進(jìn)行事務(wù)管理雄可;

  • Java原生API事務(wù):JtaTransactionManager 將事務(wù)管理的責(zé)任委托給 javax.transaction.UserTransaction 和 javax.transaction.TransactionManager 對象凿傅,其中事務(wù)成功完成通過 UserTransaction.commit() 方法提交,事務(wù)失敗通過 UserTransaction.rollback() 方法回滾数苫。

TransactionDefinition

這個接口定義了事務(wù)的屬性:

public interface TransactionDefinition {
    int getPropagationBehavior(); // 返回事務(wù)的傳播行為
    int getIsolationLevel(); // 返回事務(wù)的隔離級別聪舒,事務(wù)管理器根據(jù)它來控制另外一個事務(wù)可以看到本事務(wù)內(nèi)的哪些數(shù)據(jù)
    int getTimeout();  // 返回事務(wù)必須在多少秒內(nèi)完成
    boolean isReadOnly(); // 事務(wù)是否只讀,事務(wù)管理器能夠根據(jù)這個返回值進(jìn)行優(yōu)化虐急,確保事務(wù)是只讀的
} 

這里的幾種屬性需要我們進(jìn)行深入的理解箱残,這樣在使用 Spring transaction 時才能真正的做到得心應(yīng)手。

事務(wù)的傳播行為

事務(wù)的傳播行為描述了在一個事務(wù)方法中調(diào)用另外一個事務(wù)方法時的行為止吁。我們先來看看事務(wù)傳播行為有哪些:

傳播行為 含義
PROPAGATION_REQUIRED 表示當(dāng)前方法必須運(yùn)行在事務(wù)中被辑。如果當(dāng)前事務(wù)存在,方法將會在該事務(wù)中運(yùn)行赏殃。否則敷待,會啟動一個新的事務(wù)
PROPAGATION_SUPPORTS 表示當(dāng)前方法不需要事務(wù)上下文,但是如果存在當(dāng)前事務(wù)的話仁热,那么該方法會在這個事務(wù)中運(yùn)行
PROPAGATION_MANDATORY 表示該方法必須在事務(wù)中運(yùn)行榜揖,如果當(dāng)前事務(wù)不存在勾哩,則會拋出一個異常
PROPAGATION_REQUIRED_NEW 表示當(dāng)前方法必須運(yùn)行在它自己的事務(wù)中。一個新的事務(wù)將被啟動举哟。如果存在當(dāng)前事務(wù)思劳,在該方法執(zhí)行期間,當(dāng)前事務(wù)會被掛起妨猩。如果使用JTATransactionManager的話潜叛,則需要訪問TransactionManager
PROPAGATION_NOT_SUPPORTED 表示該方法不應(yīng)該運(yùn)行在事務(wù)中。如果存在當(dāng)前事務(wù)壶硅,在該方法運(yùn)行期間威兜,當(dāng)前事務(wù)將被掛起。如果使用JTATransactionManager的話庐椒,則需要訪問TransactionManager
PROPAGATION_NEVER 表示當(dāng)前方法不應(yīng)該運(yùn)行在事務(wù)上下文中椒舵。如果當(dāng)前正有一個事務(wù)在運(yùn)行,則會拋出異常
PROPAGATION_NESTED 表示如果當(dāng)前已經(jīng)存在一個事務(wù)约谈,那么該方法將會在嵌套事務(wù)中運(yùn)行笔宿。嵌套的事務(wù)可以獨(dú)立于當(dāng)前事務(wù)進(jìn)行單獨(dú)地提交或回滾。如果當(dāng)前事務(wù)不存在棱诱,那么其行為與PROPAGATION_REQUIRED一樣泼橘。注意各廠商對這種傳播行為的支持是有所差異的÷跹可以參考資源管理器的文檔來確認(rèn)它們是否支持嵌套事務(wù)

大部分事務(wù)傳播行為都很容易理解炬灭,這里著重說明下面幾個可能:

(1)PROPAGATION_REQUIRED:

//PROPAGATION_REQUIRED
methodA{
    ……
    //PROPAGATION_REQUIRED
    methodB();
    ……
}

如果單獨(dú)調(diào)用 methodB,相當(dāng)于

  Connection con=null; 
    try{ 
        con = getConnection(); 
        con.setAutoCommit(false); 

        //方法調(diào)用
        methodB(); 

        //提交事務(wù)
        con.commit(); 
    } Catch(RuntimeException ex) { 
        //回滾事務(wù)
        con.rollback();   
    } finally { 
        //釋放資源
        closeCon(); 
    } 

但是當(dāng)在 methodA 中調(diào)用 methodB 時粪躬,由于 methodA 已經(jīng)在一個事務(wù)中担败,所以調(diào)用 methodB 之前就不會開啟一個新的事務(wù)了:

    Connection con = null; 
    try{ 
        con = getConnection(); 
        methodA(); 
        con.commit(); 
    } catch(RuntimeException ex) { 
        con.rollback(); 
    } finally {    
        closeCon(); 
    }  

(2) PROPAGATION_REQUIRES_NEW

/事務(wù)屬性 PROPAGATION_REQUIRED
methodA(){
    doSomeThingA();
    methodB();
    doSomeThingB();
}

//事務(wù)屬性 PROPAGATION_REQUIRES_NEW
methodB(){
    ……
}

調(diào)用 methodA 相當(dāng)于:

    TransactionManager tm = null;
    try{
        //獲得一個JTA事務(wù)管理器
        tm = getTransactionManager();
        tm.begin();//開啟一個新的事務(wù)
        Transaction ts1 = tm.getTransaction();
        doSomeThing();
        tm.suspend();//掛起當(dāng)前事務(wù)
        try{
            tm.begin();//重新開啟第二個事務(wù)
            Transaction ts2 = tm.getTransaction();
            methodB();
            ts2.commit();//提交第二個事務(wù)
        } Catch(RunTimeException ex) {
            ts2.rollback();//回滾第二個事務(wù)
        } finally {
            //釋放資源
        }
        //methodB執(zhí)行完后,恢復(fù)第一個事務(wù)
        tm.resume(ts1);
        doSomeThingB();
        ts1.commit();//提交第一個事務(wù)
    } catch(RunTimeException ex) {
        ts1.rollback();//回滾第一個事務(wù)
    } finally {
        //釋放資源
    }

在 methodA 中執(zhí)行 methodB 時镰官,先掛起已經(jīng)存在的事務(wù)提前,然后開啟一個新的事務(wù),新事務(wù)和之前的事務(wù)之間并沒有任何關(guān)系泳唠,可以不同時成功或失敗狈网。

(3) PROPAGATION_NESTED

注意,使用 PROPAGATION_NESTED笨腥,需要把 PlatformTransactionManager 的 nestedTransactionAllowed 屬性設(shè)為 true拓哺。

//事務(wù)屬性 PROPAGATION_REQUIRED
methodA(){
    doSomeThingA();
    methodB();
    doSomeThingB();
}

//事務(wù)屬性 PROPAGATION_NESTED
methodB(){
    ……
}

相當(dāng)于

    Connection con = null;
    Savepoint savepoint = null;
    try{
        con = getConnection();
        con.setAutoCommit(false);
        doSomeThingA();
        savepoint = con.setSavepoint();
        try{
            methodB();
        } catch(RuntimeException ex) {
            con.rollback(savepoint);
        } finally {
            //釋放資源
        }
        doSomeThingB();
        con.commit();
    } catch(RuntimeException ex) {
        con.rollback();
    } finally {
        //釋放資源
    }

注意,當(dāng) methodB 完成時脖母,并沒有提交事務(wù)士鸥,而是當(dāng)全部操作完成后一次提交。這就是 PROPAGATION_NESTED 和 PROPAGATION_REQUIRED_NEW 的區(qū)別:嵌套事務(wù)的失敗不影響外層事務(wù)谆级,但是外層事務(wù)的失敗將導(dǎo)致嵌套事務(wù)同時被回滾烤礁。并且讼积,嵌套事務(wù)和外層事務(wù)是同時被提交的。

隔離級別

隔離級別 含義
ISOLATION_DEFAULT 使用后端數(shù)據(jù)庫默認(rèn)的隔離級別
ISOLATION_READ_UNCOMMITTED 最低的隔離級別脚仔,允許讀取尚未提交的數(shù)據(jù)變更勤众,可能會導(dǎo)致臟讀、幻讀或不可重復(fù)讀
ISOLATION_READ_COMMITTED 允許讀取并發(fā)事務(wù)已經(jīng)提交的數(shù)據(jù)鲤脏,可以阻止臟讀们颜,但是幻讀或不可重復(fù)讀仍有可能發(fā)生
ISOLATION_REPEATABLE_READ 對同一字段的多次讀取結(jié)果都是一致的,除非數(shù)據(jù)是被本身事務(wù)自己所修改猎醇,可以阻止臟讀和不可重復(fù)讀窥突,但幻讀仍有可能發(fā)生
ISOLATION_SERIALIZABLE 最高的隔離級別,完全服從ACID的隔離級別硫嘶,確保阻止臟讀波岛、不可重復(fù)讀以及幻讀,也是最慢的事務(wù)隔離級別音半,因為它通常是通過完全鎖定事務(wù)相關(guān)的數(shù)據(jù)庫表來實現(xiàn)的

上面的隔離級別和 SQL 標(biāo)準(zhǔn)的 4 種隔離級別一一對應(yīng)。對于我們常用的 Mysql贡蓖,其隔離級別為 REPEATABLE_READ曹鸠。要理解隔離級別,最簡單的辦法就是進(jìn)行實驗斥铺,下面我們就以 Mysql 為例彻桃,說明不同的隔離級別:

(1) Read Uncommitted

#將隔離級別設(shè)為 READ-UNCOMMITTED
set tx_isolation='READ-UNCOMMITTED';
select @@tx_isolation;
+------------------+
| @@tx_isolation   |
+------------------+
| READ-UNCOMMITTED |
+------------------+

#事務(wù)A:啟動一個事務(wù)
start transaction;
select * from tx;
+------+------+
| id   | num  |
+------+------+
|    1 |    1 |
|    2 |    2 |
|    3 |    3 |
+------+------+

#事務(wù)B:也啟動一個事務(wù)(那么兩個事務(wù)交叉了)
       在事務(wù)B中執(zhí)行更新語句,且不提交
start transaction;
update tx set num=10 where id=1;
select * from tx;
+------+------+
| id   | num  |
+------+------+
|    1 |   10 |
|    2 |    2 |
|    3 |    3 |
+------+------+

#事務(wù)A:那么這時候事務(wù)A能看到這個更新了的數(shù)據(jù)嗎?
select * from tx;
+------+------+
| id   | num  |
+------+------+
|    1 |   10 |   --->可以看到晾蜘!讀到了事務(wù)B還沒有提交的數(shù)據(jù)
|    2 |    2 |
|    3 |    3 |
+------+------+

這種隔離級別的問題是臟讀邻眷,即會讀到其他事務(wù)尚未提交的數(shù)據(jù),一般不會使用這種隔離級別剔交。

(2)Read Committed

#首先修改隔離級別
set tx_isolation='read-committed';
select @@tx_isolation;
+----------------+
| @@tx_isolation |
+----------------+
| READ-COMMITTED |
+----------------+

#事務(wù)A:啟動一個事務(wù)
start transaction;
select * from tx;
+------+------+
| id   | num  |
+------+------+
|    1 |    1 |
|    2 |    2 |
|    3 |    3 |
+------+------+

#事務(wù)B:也啟動一個事務(wù)(那么兩個事務(wù)交叉了)
       在這事務(wù)中更新數(shù)據(jù)肆饶,且未提交
start transaction;
update tx set num=10 where id=1;
select * from tx;
+------+------+
| id   | num  |
+------+------+
|    1 |   10 |
|    2 |    2 |
|    3 |    3 |
+------+------+

#事務(wù)A:這個時候我們在事務(wù)A中能看到數(shù)據(jù)的變化嗎?
select * from tx; --------------->
+------+------+                
| id   | num  |                
+------+------+                
|    1 |    1 |--->并不能看到!  
|    2 |    2 |                
|    3 |    3 |                
+------+------+ --->相同的select語句岖常,結(jié)果卻不一樣
                                  
#事務(wù)B:如果提交了事務(wù)B呢?            
commit;                           
                                  
#事務(wù)A:                           
select * from tx; --------------->
+------+------+
| id   | num  |
+------+------+
|    1 |   10 |--->因為事務(wù)B已經(jīng)提交了驯镊,所以在A中我們看到了數(shù)據(jù)變化
|    2 |    2 |
|    3 |    3 |
+------+------+

這種隔離級別會讀到其他事務(wù)已經(jīng)提交的數(shù)據(jù),也就是會有不可重復(fù)讀的問題竭鞍。

(3) Repeatable Read

#首先板惑,更改隔離級別
set tx_isolation='repeatable-read';
select @@tx_isolation;
+-----------------+
| @@tx_isolation  |
+-----------------+
| REPEATABLE-READ |
+-----------------+

#事務(wù)A:啟動一個事務(wù)
start transaction;
select * from tx;
+------+------+
| id   | num  |
+------+------+
|    1 |    1 |
|    2 |    2 |
|    3 |    3 |
+------+------+

#事務(wù)B:開啟一個新事務(wù)(那么這兩個事務(wù)交叉了)
       在事務(wù)B中更新數(shù)據(jù),并提交
start transaction;
update tx set num=10 where id=1;
select * from tx;
+------+------+
| id   | num  |
+------+------+
|    1 |   10 |
|    2 |    2 |
|    3 |    3 |
+------+------+
commit;

#事務(wù)A:這時候即使事務(wù)B已經(jīng)提交了,但A能不能看到數(shù)據(jù)變化偎快?
select * from tx;
+------+------+
| id   | num  |
+------+------+
|    1 |    1 | --->還是看不到的冯乘!(這個級別2不一樣,也說明級別3解決了不可重復(fù)讀問題)
|    2 |    2 |
|    3 |    3 |
+------+------+

#事務(wù)A:只有當(dāng)事務(wù)A也提交了晒夹,它才能夠看到數(shù)據(jù)變化
commit;
select * from tx;
+------+------+
| id   | num  |
+------+------+
|    1 |   10 |
|    2 |    2 |
|    3 |    3 |
+------+------+

這個隔離級別是 Mysql 的默認(rèn)隔離級別裆馒,可以解決臟讀和不可重復(fù)讀的問題姊氓,但是仍然會有幻讀的問題。什么叫幻讀领追?即在事務(wù)進(jìn)行過程中他膳,其他操作刪除或新增了記錄,這是某個查詢可能返回數(shù)據(jù)的條目變多或變少绒窑。

(4) Serializable

這個隔離級別就是將事務(wù)排隊進(jìn)行棕孙,對事務(wù)影響到的數(shù)據(jù)加了讀鎖,不允許其他事務(wù)修改數(shù)據(jù)些膨,從而解決了幻讀的問題蟀俊,但是可能導(dǎo)致大量的競爭從而引起超時。

#首先修改隔離界別
set tx_isolation='serializable';
select @@tx_isolation;
+----------------+
| @@tx_isolation |
+----------------+
| SERIALIZABLE   |
+----------------+

#事務(wù)A:開啟一個新事務(wù)
start transaction;

#事務(wù)B:在A沒有commit之前订雾,這個交叉事務(wù)是不能更改數(shù)據(jù)的
start transaction;
insert tx values('4','4');
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
update tx set num=10 where id=1;
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction

只讀

如果該事務(wù)只是讀取數(shù)據(jù)肢预,則可以指定其為只讀,方便數(shù)據(jù)庫對其進(jìn)行優(yōu)化洼哎,加快執(zhí)行效率烫映。

事務(wù)超時

指定事務(wù)的超時時間,超過則回滾噩峦。

回滾規(guī)則

除了 TransactionDefinition 中定義的屬性外锭沟,Spring 聲明式的事務(wù)處理機(jī)制中還有一個可配置項,回滾規(guī)則识补。默認(rèn)情況下族淮,事務(wù)只有遇到運(yùn)行期異常時才會回滾,而在遇到檢查型異常時不會回滾但是你可以聲明事務(wù)在遇到特定的檢查型異常時像遇到運(yùn)行期異常那樣回滾凭涂。同樣祝辣,你還可以聲明事務(wù)遇到特定的異常不回滾。

TransactionStatus

public interface TransactionStatus{
    boolean isNewTransaction(); // 是否是新的事物
    boolean hasSavepoint(); // 是否有恢復(fù)點(diǎn)
    void setRollbackOnly();  // 設(shè)置為只回滾
    boolean isRollbackOnly(); // 是否為只回滾
    boolean isCompleted; // 是否已完成
} 

3 Spring 中使用事務(wù)


Spring 中使用事務(wù)有兩種方法:直接編程實現(xiàn)切油,或是通過配置進(jìn)行聲明式的實現(xiàn)蝙斜。編程式事務(wù)侵入到了業(yè)務(wù)代碼里面,但是提供了更加詳細(xì)的事務(wù)管理澎胡;聲明式事務(wù)基于AOP乍炉,所以既能起到事務(wù)管理的作用,又可以不影響業(yè)務(wù)代碼的具體實現(xiàn)滤馍。在實際使用中可以根據(jù)需要自行選擇實現(xiàn)方式岛琼。

由于聲明式事務(wù)最終也是由編程式事務(wù)實現(xiàn)的,先來了解一下編程式事務(wù)是如何實現(xiàn)的巢株。

編程式事務(wù)

Spring提供兩種方式的編程式事務(wù)管理槐瑞,分別是:使用 TransactionTemplate 和直接使用 PlatformTransactionManager。

TransactionTemplate

TransactionTemplate 類似于 JdbcTemplate阁苞,使用模板方法封裝了繁瑣的細(xì)節(jié)困檩,可以通過提供回調(diào)方法來執(zhí)行所需執(zhí)行的代碼:

   TransactionTemplate tt = new TransactionTemplate(); // 新建一個TransactionTemplate
    Object result = tt.execute(
        new TransactionCallback(){  
            public Object doTransaction(TransactionStatus status){  
                updateOperation();  
                return resultOfUpdateOperation();  
            }  
    }); 

使用TransactionCallback()可以返回一個值祠挫。如果使用TransactionCallbackWithoutResult則沒有返回值。

PlatformTransactionManager

 DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager(); 
    dataSourceTransactionManager.setDataSource(this.getJdbcTemplate().getDataSource()); // 設(shè)置數(shù)據(jù)源
    DefaultTransactionDefinition transDef = new DefaultTransactionDefinition(); // 定義事務(wù)屬性
    transDef.setPropagationBehavior(DefaultTransactionDefinition.PROPAGATION_REQUIRED); // 設(shè)置傳播行為屬性
    TransactionStatus status = dataSourceTransactionManager.getTransaction(transDef); // 獲得事務(wù)狀態(tài)
    try {
        // 數(shù)據(jù)庫操作
        dataSourceTransactionManager.commit(status);// 提交
    } catch (Exception e) {
        dataSourceTransactionManager.rollback(status);// 回滾
    }

聲明式事務(wù)

聲明式事務(wù)管理建立在AOP之上的悼沿。其本質(zhì)是對方法前后進(jìn)行攔截等舔,然后在目標(biāo)方法開始之前創(chuàng)建或者加入一個事務(wù),在執(zhí)行完目標(biāo)方法之后根據(jù)執(zhí)行情況提交或者回滾事務(wù)糟趾。

下面提供幾種配置聲明式事務(wù)的示例慌植。

每個Bean都有一個代理

 <bean id="sessionFactory" 
            class="org.springframework.orm.hibernate3.LocalSessionFactoryBean"> 
        <property name="configLocation" value="classpath:hibernate.cfg.xml" /> 
        <property name="configurationClass" value="org.hibernate.cfg.AnnotationConfiguration" />
    </bean> 

    <!-- 定義事務(wù)管理器(聲明式的事務(wù)) --> 
    <bean id="transactionManager"
        class="org.springframework.orm.hibernate3.HibernateTransactionManager">
        <property name="sessionFactory" ref="sessionFactory" />
    </bean>

    <!-- 配置DAO -->
    <bean id="userDaoTarget" class="com.bluesky.spring.dao.UserDaoImpl">
        <property name="sessionFactory" ref="sessionFactory" />
    </bean>

    <bean id="userDao" 
        class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean"> 
           <!-- 配置事務(wù)管理器 --> 
         <property name="transactionManager" ref="transactionManager" />    
         <property name="target" ref="userDaoTarget" /> 
         <property name="proxyInterfaces" value="com.bluesky.spring.dao.GeneratorDao" />
         <!-- 配置事務(wù)屬性 --> 
         <property name="transactionAttributes"> 
            <props> 
                <prop key="*">PROPAGATION_REQUIRED</prop>
            </props> 
        </property> 
    </bean> 

所有bean 共享一個 abstract bean

    <bean id="sessionFactory" 
            class="org.springframework.orm.hibernate3.LocalSessionFactoryBean"> 
        <property name="configLocation" value="classpath:hibernate.cfg.xml" /> 
        <property name="configurationClass" value="org.hibernate.cfg.AnnotationConfiguration" />
    </bean> 

    <!-- 定義事務(wù)管理器(聲明式的事務(wù)) --> 
    <bean id="transactionManager"
        class="org.springframework.orm.hibernate3.HibernateTransactionManager">
        <property name="sessionFactory" ref="sessionFactory" />
    </bean>

    <bean id="transactionBase" 
            class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean" 
            lazy-init="true" abstract="true"> 
        <!-- 配置事務(wù)管理器 --> 
        <property name="transactionManager" ref="transactionManager" /> 
        <!-- 配置事務(wù)屬性 --> 
        <property name="transactionAttributes"> 
            <props> 
                <prop key="*">PROPAGATION_REQUIRED</prop> 
            </props> 
        </property> 
    </bean>   

    <!-- 配置DAO -->
    <bean id="userDaoTarget" class="com.bluesky.spring.dao.UserDaoImpl">
        <property name="sessionFactory" ref="sessionFactory" />
    </bean>

    <bean id="userDao" parent="transactionBase" > 
        <property name="target" ref="userDaoTarget" />  
    </bean>

使用攔截器

    <bean id="sessionFactory" 
            class="org.springframework.orm.hibernate3.LocalSessionFactoryBean"> 
        <property name="configLocation" value="classpath:hibernate.cfg.xml" /> 
        <property name="configurationClass" value="org.hibernate.cfg.AnnotationConfiguration" />
    </bean> 

    <!-- 定義事務(wù)管理器(聲明式的事務(wù)) --> 
    <bean id="transactionManager"
        class="org.springframework.orm.hibernate3.HibernateTransactionManager">
        <property name="sessionFactory" ref="sessionFactory" />
    </bean> 

    <bean id="transactionInterceptor" 
        class="org.springframework.transaction.interceptor.TransactionInterceptor"> 
        <property name="transactionManager" ref="transactionManager" /> 
        <!-- 配置事務(wù)屬性 --> 
        <property name="transactionAttributes"> 
            <props> 
                <prop key="*">PROPAGATION_REQUIRED</prop> 
            </props> 
        </property> 
    </bean>

    <bean class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator"> 
        <property name="beanNames"> 
            <list> 
                <value>*Dao</value>
            </list> 
        </property> 
        <property name="interceptorNames"> 
            <list> 
                <value>transactionInterceptor</value> 
            </list> 
        </property> 
    </bean> 

    <!-- 配置DAO -->
    <bean id="userDao" class="com.bluesky.spring.dao.UserDaoImpl">
        <property name="sessionFactory" ref="sessionFactory" />
    </bean>

使用tx標(biāo)簽配置的攔截器

    <context:annotation-config />
    <context:component-scan base-package="com.bluesky" />

    <bean id="sessionFactory" 
            class="org.springframework.orm.hibernate3.LocalSessionFactoryBean"> 
        <property name="configLocation" value="classpath:hibernate.cfg.xml" /> 
        <property name="configurationClass" value="org.hibernate.cfg.AnnotationConfiguration" />
    </bean> 

    <!-- 定義事務(wù)管理器(聲明式的事務(wù)) --> 
    <bean id="transactionManager"
        class="org.springframework.orm.hibernate3.HibernateTransactionManager">
        <property name="sessionFactory" ref="sessionFactory" />
    </bean>

    <tx:advice id="txAdvice" transaction-manager="transactionManager">
        <tx:attributes>
            <tx:method name="*" propagation="REQUIRED" />
        </tx:attributes>
    </tx:advice>

    <aop:config>
        <!-- 定義 pointcut com.bluesky.spring.dao 包下的所有類的所有方法  --> 
        <aop:pointcut id="interceptorPointCuts"
            expression="execution(* com.bluesky.spring.dao.*.*(..))" />
        <aop:advisor advice-ref="txAdvice"
            pointcut-ref="interceptorPointCuts" />       
    </aop:config>   

純注解(使用mybatis)

  <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">  
        <property name="dataSource" ref="dataSource" />  
        <property name="configLocation">  
            <value>classpath:mybatis-config.xml</value>  
        </property>  
    </bean>  
      
    <!-- mybatis mappers, scanned automatically -->  
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">  
        <property name="basePackage">  
              <value>com.bluesky.spring.dao</value>  
        </property>  
        <property name="sqlSessionFactory" ref="sqlSessionFactory" />  
    </bean>  
      
    <!-- 配置spring的PlatformTransactionManager,名字為默認(rèn)值 -->  
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">  
        <property name="dataSource" ref="dataSource" />  
    </bean>  
      
    <!-- 開啟事務(wù)控制的注解支持 -->  
    <tx:annotation-driven transaction-manager="transactionManager"/>

而后义郑,就可以在想要使用事務(wù)的方法上使用 @Transactional 注解了蝶柿。 @Transactional 可以作用于接口、接口方法非驮、類以及類方法上交汤。當(dāng)作用于類上時,該類的所有 public 方法將都具有該類型的事務(wù)屬性劫笙,同時芙扎,我們也可以在方法級別使用該標(biāo)注來覆蓋類級別的定義。同時填大,Spring 建議不要在接口或接口方法上使用該注解纵顾,因為這只有在使用基于接口的代理時它才會生效。

package com.springinaction.transaction;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

@Service("bookShopService")
public class BookShopServiceImpl implements BookShopService {

    @Autowired
    private BookShopDao bookShopDao;

    /**
     * 1.添加事務(wù)注解
     * 使用propagation 指定事務(wù)的傳播行為栋盹,即當(dāng)前的事務(wù)方法被另外一個事務(wù)方法調(diào)用時如何使用事務(wù)。
     * 默認(rèn)取值為REQUIRED敷矫,即使用調(diào)用方法的事務(wù)
     * REQUIRES_NEW:使用自己的事務(wù)例获,調(diào)用的事務(wù)方法的事務(wù)被掛起。
     *
     * 2.使用isolation 指定事務(wù)的隔離級別曹仗,最常用的取值為READ_COMMITTED
     * 3.默認(rèn)情況下 Spring 的聲明式事務(wù)對所有的運(yùn)行時異常進(jìn)行回滾榨汤,也可以通過對應(yīng)的屬性進(jìn)行設(shè)置。通常情況下怎茫,默認(rèn)值即可收壕。
     * 4.使用readOnly 指定事務(wù)是否為只讀。 表示這個事務(wù)只讀取數(shù)據(jù)但不更新數(shù)據(jù)轨蛤,這樣可以幫助數(shù)據(jù)庫引擎優(yōu)化事務(wù)蜜宪。若真的是一個只讀取數(shù)據(jù)庫值得方法,應(yīng)設(shè)置readOnly=true
     * 5.使用timeOut 指定強(qiáng)制回滾之前事務(wù)可以占用的時間祥山。
     */
    @Transactional(propagation=Propagation.REQUIRES_NEW,
            isolation=Isolation.READ_COMMITTED,
            noRollbackFor={UserAccountException.class},
            readOnly=false, timeout=3)
    @Override
    public void purchase(String username, String isbn) {
        //1.獲取書的單價
        int price = bookShopDao.findBookPriceByIsbn(isbn);
        //2.更新書的庫存
        bookShopDao.updateBookStock(isbn);
        //3.更新用戶余額
        bookShopDao.updateUserAccount(username, price);
    }
}

以上圃验。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市缝呕,隨后出現(xiàn)的幾起案子澳窑,更是在濱河造成了極大的恐慌斧散,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,214評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件摊聋,死亡現(xiàn)場離奇詭異鸡捐,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)麻裁,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,307評論 2 382
  • 文/潘曉璐 我一進(jìn)店門箍镜,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人悲立,你說我怎么就攤上這事鹿寨。” “怎么了薪夕?”我有些...
    開封第一講書人閱讀 152,543評論 0 341
  • 文/不壞的土叔 我叫張陵脚草,是天一觀的道長。 經(jīng)常有香客問我原献,道長馏慨,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,221評論 1 279
  • 正文 為了忘掉前任姑隅,我火速辦了婚禮写隶,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘讲仰。我一直安慰自己慕趴,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,224評論 5 371
  • 文/花漫 我一把揭開白布鄙陡。 她就那樣靜靜地躺著冕房,像睡著了一般。 火紅的嫁衣襯著肌膚如雪趁矾。 梳的紋絲不亂的頭發(fā)上耙册,一...
    開封第一講書人閱讀 49,007評論 1 284
  • 那天,我揣著相機(jī)與錄音毫捣,去河邊找鬼详拙。 笑死,一個胖子當(dāng)著我的面吹牛蔓同,可吹牛的內(nèi)容都是我干的饶辙。 我是一名探鬼主播,決...
    沈念sama閱讀 38,313評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼斑粱,長吁一口氣:“原來是場噩夢啊……” “哼畸悬!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,956評論 0 259
  • 序言:老撾萬榮一對情侶失蹤蹋宦,失蹤者是張志新(化名)和其女友劉穎披粟,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體冷冗,經(jīng)...
    沈念sama閱讀 43,441評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡守屉,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,925評論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了蒿辙。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片拇泛。...
    茶點(diǎn)故事閱讀 38,018評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖思灌,靈堂內(nèi)的尸體忽然破棺而出俺叭,到底是詐尸還是另有隱情,我是刑警寧澤泰偿,帶...
    沈念sama閱讀 33,685評論 4 322
  • 正文 年R本政府宣布熄守,位于F島的核電站,受9級特大地震影響耗跛,放射性物質(zhì)發(fā)生泄漏裕照。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,234評論 3 307
  • 文/蒙蒙 一调塌、第九天 我趴在偏房一處隱蔽的房頂上張望晋南。 院中可真熱鬧,春花似錦羔砾、人聲如沸负间。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,240評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽政溃。三九已至,卻和暖如春檀葛,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背腹缩。 一陣腳步聲響...
    開封第一講書人閱讀 31,464評論 1 261
  • 我被黑心中介騙來泰國打工屿聋, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人藏鹊。 一個月前我還...
    沈念sama閱讀 45,467評論 2 352
  • 正文 我出身青樓润讥,卻偏偏與公主長得像,于是被迫代替她去往敵國和親盘寡。 傳聞我的和親對象是個殘疾皇子楚殿,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,762評論 2 345

推薦閱讀更多精彩內(nèi)容

  • spring支持編程式事務(wù)管理和聲明式事務(wù)管理兩種方式。 編程式事務(wù)管理使用TransactionTemplate...
    熊熊要更努力閱讀 245評論 0 0
  • 對大多數(shù)Java開發(fā)者來說竿痰,Spring事務(wù)管理是Spring應(yīng)用中最常用的功能脆粥,使用也比較簡單砌溺。本文主要從三個方...
    sherlockyb閱讀 3,194評論 0 18
  • Spring事務(wù)機(jī)制主要包括聲明式事務(wù)和編程式事務(wù),此處側(cè)重講解聲明式事務(wù)变隔,編程式事務(wù)在實際開發(fā)中得不到廣泛使用规伐,...
    EnigmaXXX閱讀 667評論 0 0
  • 事務(wù)簡介 所謂事務(wù),指的是程序中可運(yùn)行的不可分割的最小單位匣缘。在生活中事務(wù)也是隨處可見的猖闪。比方說你在Steam上剁手...
    樂百川閱讀 673評論 0 5
  • 很多人喜歡這篇文章,特此同步過來 由淺入深談?wù)搒pring事務(wù) 前言 這篇其實也要?dú)w納到《常識》系列中肌厨,但這重點(diǎn)又...
    碼農(nóng)戲碼閱讀 4,706評論 2 59