原文出處: 陶邦仁java
Spring事務機制主要包括聲明式事務和編程式事務,此處側重講解聲明式事務,編程式事務在實際開發中得不到普遍使用,僅供學習參考。spring
Spring聲明式事務讓咱們從複雜的事務處理中獲得解脫。使得咱們再也無須要去處理得到鏈接、關閉鏈接、事務提交和回滾等這些操做。再也無須要咱們在與事務相關的方法中處理大量的try…catch…finally代碼。咱們在使用Spring聲明式事務時,有一個很是重要的概念就是事務屬性。事務屬性一般由事務的傳播行爲,事務的隔離級別,事務的超時值和事務只讀標誌組成。咱們在進行事務劃分時,須要進行事務定義,也就是配置事務的屬性。sql
下面分別詳細講解,事務的四種屬性,僅供諸位學習參考:數據庫
Spring在TransactionDefinition接口中定義這些屬性,以供PlatfromTransactionManager使用, PlatfromTransactionManager是spring事務管理的核心接口。編程
1
2
3
4
5
6
|
public
interface
TransactionDefinition {
int
getPropagationBehavior();
//返回事務的傳播行爲。
int
getIsolationLevel();
//返回事務的隔離級別,事務管理器根據它來控制另一個事務能夠看到本事務內的哪些數據。
int
getTimeout();
//返回事務必須在多少秒內完成。
boolean
isReadOnly();
//事務是否只讀,事務管理器可以根據這個返回值進行優化,確保事務是隻讀的。
}
|
1. TransactionDefinition接口中定義五個隔離級別:緩存
ISOLATION_DEFAULT 這是一個PlatfromTransactionManager默認的隔離級別,使用數據庫默認的事務隔離級別.另外四個與JDBC的隔離級別相對應;學習
ISOLATION_READ_UNCOMMITTED 這是事務最低的隔離級別,它充許別外一個事務能夠看到這個事務未提交的數據。這種隔離級別會產生髒讀,不可重複讀和幻像讀。優化
ISOLATION_READ_COMMITTED 保證一個事務修改的數據提交後才能被另一個事務讀取。另一個事務不能讀取該事務未提交的數據。這種事務隔離級別能夠避免髒讀出現,可是可能會出現不可重複讀和幻像讀。spa
ISOLATION_REPEATABLE_READ 這種事務隔離級別能夠防止髒讀,不可重複讀。可是可能出現幻像讀。它除了保證一個事務不能讀取另外一個事務未提交的數據外,還保證了避免下面的狀況產生(不可重複讀)。.net
ISOLATION_SERIALIZABLE 這是花費最高代價可是最可靠的事務隔離級別。事務被處理爲順序執行。除了防止髒讀,不可重複讀外,還避免了幻像讀。
1: Dirty reads(髒讀)。也就是說,好比事務A的未提交(還依然緩存)的數據被事務B讀走,若是事務A失敗回滾,會致使事務B所讀取的的數據是錯誤的。
2: non-repeatable reads(數據不可重複讀)。好比事務A中兩處讀取數據-total-的值。在第一讀的時候,total是100,而後事務B就把total的數據改爲200,事務A再讀一次,結果就發現,total居然就變成200了,形成事務A數據混亂。
3: phantom reads(幻象讀數據),這個和non-repeatable reads類似,也是同一個事務中屢次讀不一致的問題。可是non-repeatable reads的不一致是由於他所要取的數據集被改變了(好比total的數據),可是phantom reads所要讀的數據的不一致卻不是他所要讀的數據集改變,而是他的條件數據集改變。好比Select account.id where account.name=」ppgogo*」,第一次讀去了6個符合條件的id,第二次讀取的時候,因爲事務b把一個賬號的名字由」dd」改爲」ppgogo1″,結果取出來了7個數據。
2. 在TransactionDefinition接口中定義了七個事務傳播行爲:
(1)PROPAGATION_REQUIRED 若是存在一個事務,則支持當前事務。若是沒有事務則開啓一個新的事務。
Java代碼:
1
2
3
4
5
6
7
8
9
10
11
|
//事務屬性 PROPAGATION_REQUIRED
methodA{
……
methodB();
……
}
//事務屬性 PROPAGATION_REQUIRED
methodB{
……
}
|
使用spring聲明式事務,spring使用AOP來支持聲明式事務,會根據事務屬性,自動在方法調用以前決定是否開啓一個事務,並在方法執行以後決定事務提交或回滾事務。
單獨調用methodB方法:
Java代碼
1
2
3
4
5
|
main{
metodB();
}
|
至關於
Java代碼
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
|
Main{
Connection con=
null
;
try
{
con = getConnection();
con.setAutoCommit(
false
);
//方法調用
methodB();
//提交事務
con.commit();
}
Catch(RuntimeException ex){
//回滾事務
con.rollback();
}
finally
{
//釋放資源
closeCon();
}
}
|
Spring保證在methodB方法中全部的調用都得到到一個相同的鏈接。在調用methodB時,沒有一個存在的事務,因此得到一個新的鏈接,開啓了一個新的事務。
單獨調用MethodA時,在MethodA內又會調用MethodB.
執行效果至關於:
Java代碼
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
main{
Connection con =
null
;
try
{
con = getConnection();
methodA();
con.commit();
}
catch
(RuntimeException ex){
con.rollback();
}
finally
{
closeCon();
}
}
|
調用MethodA時,環境中沒有事務,因此開啓一個新的事務.當在MethodA中調用MethodB時,環境中已經有了一個事務,因此methodB就加入當前事務。
(2)PROPAGATION_SUPPORTS 若是存在一個事務,支持當前事務。若是沒有事務,則非事務的執行。可是對於事務同步的事務管理器,PROPAGATION_SUPPORTS與不使用事務有少量不一樣。
Java代碼:
1
2
3
4
5
6
7
8
9
|
//事務屬性 PROPAGATION_REQUIRED
methodA(){
methodB();
}
//事務屬性 PROPAGATION_SUPPORTS
methodB(){
……
}
|
單純的調用methodB時,methodB方法是非事務的執行的。當調用methdA時,methodB則加入了methodA的事務中,事務地執行。
(3)PROPAGATION_MANDATORY 若是已經存在一個事務,支持當前事務。若是沒有一個活動的事務,則拋出異常。
Java代碼:
1
2
3
4
5
6
7
8
9
|
//事務屬性 PROPAGATION_REQUIRED
methodA(){
methodB();
}
//事務屬性 PROPAGATION_MANDATORY
methodB(){
……
}
|
當單獨調用methodB時,由於當前沒有一個活動的事務,則會拋出異常throw new IllegalTransactionStateException(「Transaction propagation ‘mandatory’ but no existing transaction found」);當調用methodA時,methodB則加入到methodA的事務中,事務地執行。
(4)PROPAGATION_REQUIRES_NEW 老是開啓一個新的事務。若是一個事務已經存在,則將這個存在的事務掛起。
Java代碼:
1
2
3
4
5
6
7
8
9
10
11
|
//事務屬性 PROPAGATION_REQUIRED
methodA(){
doSomeThingA();
methodB();
doSomeThingB();
}
//事務屬性 PROPAGATION_REQUIRES_NEW
methodB(){
……
}
|
Java代碼:
1
2
3
|
main(){
methodA();
}
|
至關於
Java代碼:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
|
main(){
TransactionManager tm =
null
;
try
{
//得到一個JTA事務管理器
tm = getTransactionManager();
tm.begin();
//開啓一個新的事務
Transaction ts1 = tm.getTransaction();
doSomeThing();
tm.suspend();
//掛起當前事務
try
{
tm.begin();
//從新開啓第二個事務
Transaction ts2 = tm.getTransaction();
methodB();
ts2.commit();
//提交第二個事務
}
Catch(RunTimeException ex){
ts2.rollback();
//回滾第二個事務
}
finally
{
//釋放資源
}
//methodB執行完後,復恢第一個事務
tm.resume(ts1);
doSomeThingB();
ts1.commit();
//提交第一個事務
}
catch
(RunTimeException ex){
ts1.rollback();
//回滾第一個事務
}
finally
{
//釋放資源
}
}
|
在這裏,我把ts1稱爲外層事務,ts2稱爲內層事務。從上面的代碼能夠看出,ts2與ts1是兩個獨立的事務,互不相干。Ts2是否成功並不依賴於ts1。若是methodA方法在調用methodB方法後的doSomeThingB方法失敗了,而methodB方法所作的結果依然被提交。而除了methodB以外的其它代碼致使的結果卻被回滾了。使用PROPAGATION_REQUIRES_NEW,須要使用JtaTransactionManager做爲事務管理器。
(5)PROPAGATION_NOT_SUPPORTED 老是非事務地執行,並掛起任何存在的事務。使用PROPAGATION_NOT_SUPPORTED,也須要使用JtaTransactionManager做爲事務管理器。(代碼示例同上,可同理推出)
(6)PROPAGATION_NEVER 老是非事務地執行,若是存在一個活動事務,則拋出異常;
(7)PROPAGATION_NESTED若是一個活動的事務存在,則運行在一個嵌套的事務中. 若是沒有活動事務, 則按TransactionDefinition.PROPAGATION_REQUIRED 屬性執行。這是一個嵌套事務,使用JDBC 3.0驅動時,僅僅支持DataSourceTransactionManager做爲事務管理器。須要JDBC 驅動的java.sql.Savepoint類。有一些JTA的事務管理器實現可能也提供了一樣的功能。使用PROPAGATION_NESTED,還須要把PlatformTransactionManager的nestedTransactionAllowed屬性設爲true;而nestedTransactionAllowed屬性值默認爲false;
Java代碼:
1
2
3
4
5
6
7
8
9
10
11
|
//事務屬性 PROPAGATION_REQUIRED
methodA(){
doSomeThingA();
methodB();
doSomeThingB();
}
//事務屬性 PROPAGATION_NESTED
methodB(){
……
}
|
若是單獨調用methodB方法,則按REQUIRED屬性執行。若是調用methodA方法,至關於下面的效果:
Java代碼:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
main(){
Connection con =
null
;
Savepoint savepoint =
null
;
try
{
con = getConnection();
con.setAutoCommit(
false
);
doSomeThingA();
savepoint = con2.setSavepoint();
try
{
methodB();
}
catch
(RuntimeException ex){
con.rollback(savepoint);
}
finally
{
//釋放資源
}
doSomeThingB();
con.commit();
}
catch
(RuntimeException ex){
con.rollback();
}
finally
{
//釋放資源
}
}
|
當methodB方法調用以前,調用setSavepoint方法,保存當前的狀態到savepoint。若是methodB方法調用失敗,則恢復到以前保存的狀態。可是須要注意的是,這時的事務並無進行提交,若是後續的代碼(doSomeThingB()方法)調用失敗,則回滾包括methodB方法的全部操做。
嵌套事務一個很是重要的概念就是內層事務依賴於外層事務。外層事務失敗時,會回滾內層事務所作的動做。而內層事務操做失敗並不會引發外層事務的回滾。
PROPAGATION_NESTED 與PROPAGATION_REQUIRES_NEW的區別:它們很是相似,都像一個嵌套事務,若是不存在一個活動的事務,都會開啓一個新的事務。使用PROPAGATION_REQUIRES_NEW時,內層事務與外層事務就像兩個獨立的事務同樣,一旦內層事務進行了提交後,外層事務不能對其進行回滾。兩個事務互不影響。兩個事務不是一個真正的嵌套事務。同時它須要JTA事務管理器的支持。
使用PROPAGATION_NESTED時,外層事務的回滾能夠引發內層事務的回滾。而內層事務的異常並不會致使外層事務的回滾,它是一個真正的嵌套事務。DataSourceTransactionManager使用savepoint支持PROPAGATION_NESTED時,須要JDBC 3.0以上驅動及1.4以上的JDK版本支持。其它的JTA TrasactionManager實現可能有不一樣的支持方式。
PROPAGATION_REQUIRES_NEW 啓動一個新的, 不依賴於環境的 「內部」 事務. 這個事務將被徹底 commited 或 rolled back 而不依賴於外部事務, 它擁有本身的隔離範圍, 本身的鎖, 等等. 當內部事務開始執行時, 外部事務將被掛起, 內務事務結束時, 外部事務將繼續執行。
另外一方面, PROPAGATION_NESTED 開始一個 「嵌套的」 事務, 它是已經存在事務的一個真正的子事務. 潛套事務開始執行時, 它將取得一個 savepoint. 若是這個嵌套事務失敗, 咱們將回滾到此 savepoint. 潛套事務是外部事務的一部分, 只有外部事務結束後它纔會被提交。
因而可知, PROPAGATION_REQUIRES_NEW 和 PROPAGATION_NESTED 的最大區別在於, PROPAGATION_REQUIRES_NEW 徹底是一個新的事務, 而 PROPAGATION_NESTED 則是外部事務的子事務, 若是外部事務 commit, 潛套事務也會被 commit, 這個規則一樣適用於 roll back.PROPAGATION_REQUIRED應該是咱們首先的事務傳播行爲。它可以知足咱們大多數的事務需求。