1 什么是事務(wù)
生活中關(guān)于事務(wù)有一個常見的場景,即銀行用戶轉(zhuǎn)賬盒揉。簡單的講前计,轉(zhuǎn)賬可以分為下面 2 個步驟:
- 查看用戶 A 的賬戶是否有足夠的余額,我們假設(shè)轉(zhuǎn)賬 1000 元蛔琅,如果有則從 A 賬戶中扣除 1000 元;
- 將用戶 B 的賬戶余額增加 1000 元峻呛。
很明顯罗售,上面的 2 個步驟必須同時成功或者同時失敗,否則將可能出現(xiàn)下面兩種情況:
- 用戶 A 扣了錢但用戶 B 沒有收到錢钩述,這樣用戶 A 就損失了 1000 元寨躁;
- 用戶 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ù)管理核心接口
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);
}
}
以上圃验。