這幾天找工作,面試了將近10家公司赤拒,其中有幾個問題幾乎每家公司都會問,spring的事物機制應(yīng)該算是其中最常被問到的問題诱鞠,而且這個問題你回答的好挎挖,面試官會覺得你基本功很扎實,印象直接提升一個檔次航夺。今天就來詳細分析講解一下spring的事物機制蕉朵。文章有點長,看完保證你受益匪淺阳掐。
對了始衅,先來個花絮,有一天面試被問ACID是什么缭保,當(dāng)時想臥槽汛闸,這時啥,根本不知道艺骂。后來百度了才知道他是啥
原子性(Atomicity)部蛇,一致性(Consistency)筐乳,隔離性(Isolation),持久性(Durability)
原來就是這個,瞬間石化崇败,這么簡單地問題逮矛。
好辐马,進入正題访惜,先來一張圖
Spring并不直接管理事務(wù),而是提供了多種事務(wù)管理器宙址,他們將事務(wù)管理的職責(zé)委托給Hibernate或者JTA等持久化機制所提供的相關(guān)平臺框架的事務(wù)來實現(xiàn)轴脐。
Spring事務(wù)管理器的接口是org.springframework.transaction.PlatformTransactionManager,通過這個接口抡砂,Spring為各個平臺如JDBC大咱、Hibernate等都提供了對應(yīng)的事務(wù)管理器,但是具體的實現(xiàn)就是各個平臺自己的事情了注益。此接口的內(nèi)容如下:
package org.springframework.transaction;
public interface PlatformTransactionManager {
//根據(jù)TransactionDefinition得到TransactionStatus
TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException;
//根據(jù)TransactionDefinition得到TransactionStatus
void commit(TransactionStatus status) throws TransactionException;
//根據(jù)TransactionDefinition得到TransactionStatus
void rollback(TransactionStatus status) throws TransactionException;
}
可以看到碴巾,spring是提供了3個基本的方法,然后讓各自jdbc框架自己去實現(xiàn)丑搔,所以Spring事務(wù)管理的一個優(yōu)點就是為不同的事務(wù)API提供一致的編程模型厦瓢,如JTA提揍、JDBC、Hibernate煮仇、JPA劳跃。
例如,我使用的是JDBC的事物管理
當(dāng)然如果你使用Hibernate浙垫,也可以使用Hibernate的事物管理
org.springframework.orm.hibernate3.HibernateTransactionManager
#######事物的基本屬性
在上的PlatformTransactionManager 接口中有一個方法是
TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException;
通過TransactionDefinition 來獲取事物的狀態(tài)刨仑,其中TransactionDefinition 就保存了事物的基本屬性。
下面我們來看看這個接口中的代碼
package org.springframework.transaction;
import java.sql.Connection;
public interface TransactionDefinition {
int PROPAGATION_REQUIRED = 0;
int PROPAGATION_SUPPORTS = 1;
int PROPAGATION_MANDATORY = 2;
int PROPAGATION_REQUIRES_NEW = 3;
int PROPAGATION_NOT_SUPPORTED = 4;
int PROPAGATION_NEVER = 5;
int PROPAGATION_NESTED = 6;
int ISOLATION_DEFAULT = -1;
int ISOLATION_READ_UNCOMMITTED = Connection.TRANSACTION_READ_UNCOMMITTED;
int ISOLATION_READ_COMMITTED = Connection.TRANSACTION_READ_COMMITTED;
int ISOLATION_REPEATABLE_READ = Connection.TRANSACTION_REPEATABLE_READ;
int ISOLATION_SERIALIZABLE = Connection.TRANSACTION_SERIALIZABLE;
int TIMEOUT_DEFAULT = -1;
//獲取事務(wù)的傳播行為
int getPropagationBehavior();
//獲取事務(wù)的隔離級別
int getIsolationLevel();
//獲取事務(wù)的超時時間
int getTimeout();
//獲取事務(wù)是否只讀
boolean isReadOnly();
//獲取事務(wù)的傳播行為
String getName();
上面被我注釋的4個方法夹姥,分別代表了事物的4基本基本屬性杉武,分別是,“傳播行為”辙售、“隔離級別”轻抱、“超時時間”、“只讀”旦部,其實還有第五個屬性祈搜,是“回滾規(guī)則”,這個一會會說到志鹃。
下面我們逐個分析
事務(wù)的傳播行為
事務(wù)的傳播行為是指當(dāng)事務(wù)方法被另一個事務(wù)方法調(diào)用時夭问,必須指定事務(wù)應(yīng)該如何傳播泽西。例如:方法可能繼續(xù)在現(xiàn)有事務(wù)中運行曹铃,也可能開啟一個新事務(wù),并在自己的事務(wù)中運行捧杉。Spring定義了七種傳播行為:
(1)PROPAGATION_REQUIRED:
表示當(dāng)前方法必須運行在事務(wù)中陕见。如果當(dāng)前事務(wù)存在,方法將會在該事務(wù)中運行味抖。否則评甜,會啟動一個新的事務(wù)
注:以下具體講解傳播行為的內(nèi)容參考自Spring事務(wù)機制詳解
//事務(wù)屬性 PROPAGATION_REQUIRED
methodA{
……
methodB();
……
}
//事務(wù)屬性 PROPAGATION_REQUIRED
methodB{
……
}
單獨調(diào)用methodB方法:
main{
metodB();
}
相當(dāng)于
Main{
Connection con=null;
try{
con = getConnection();
con.setAutoCommit(false);
//方法調(diào)用
methodB();
//提交事務(wù)
con.commit();
} Catch(RuntimeException ex) {
//回滾事務(wù)
con.rollback();
} finally {
//釋放資源
closeCon();
}
}
Spring保證在methodB方法中所有的調(diào)用都獲得到一個相同的連接。在調(diào)用methodB時仔涩,沒有一個存在的事務(wù)忍坷,所以獲得一個新的連接,開啟了一個新的事務(wù)熔脂。
單獨調(diào)用MethodA時佩研,在MethodA內(nèi)又會調(diào)用MethodB.
執(zhí)行效果相當(dāng)于:
main{
Connection con = null;
try{
con = getConnection();
methodA();
con.commit();
} catch(RuntimeException ex) {
con.rollback();
} finally {
closeCon();
}
}
調(diào)用MethodA時,環(huán)境中沒有事務(wù)霞揉,所以開啟一個新的事務(wù).當(dāng)在MethodA中調(diào)用MethodB時旬薯,環(huán)境中已經(jīng)有了一個事務(wù),所以methodB就加入當(dāng)前事務(wù)适秩。
(2)PROPAGATION_SUPPORTS
如果存在一個事務(wù)绊序,支持當(dāng)前事務(wù)硕舆。如果沒有事務(wù),則非事務(wù)的執(zhí)行骤公。但是對于事務(wù)同步的事務(wù)管理器抚官,PROPAGATION_SUPPORTS與不使用事務(wù)有少許不同。
//事務(wù)屬性 PROPAGATION_REQUIRED
methodA(){
methodB();
}
//事務(wù)屬性 PROPAGATION_SUPPORTS
methodB(){
……
}
單純的調(diào)用methodB時淋样,methodB方法是非事務(wù)的執(zhí)行的耗式。當(dāng)調(diào)用methdA時,methodB則加入了methodA的事務(wù)中,事務(wù)地執(zhí)行。
(3)PROPAGATION_MANDATORY
如果已經(jīng)存在一個事務(wù)趁猴,支持當(dāng)前事務(wù)刊咳。如果沒有一個活動的事務(wù),則拋出異常儡司。
//事務(wù)屬性 PROPAGATION_REQUIRED
methodA(){
methodB();
}
//事務(wù)屬性 PROPAGATION_MANDATORY
methodB(){
……
}
當(dāng)單獨調(diào)用methodB時娱挨,因為當(dāng)前沒有一個活動的事務(wù),則會拋出異常throw new IllegalTransactionStateException(“Transaction propagation ‘mandatory’ but no existing transaction found”);當(dāng)調(diào)用methodA時捕犬,methodB則加入到methodA的事務(wù)中跷坝,事務(wù)地執(zhí)行。
(4)PROPAGATION_REQUIRES_NEW
總是開啟一個新的事務(wù)碉碉。如果一個事務(wù)已經(jīng)存在柴钻,則將這個存在的事務(wù)掛起。
//事務(wù)屬性 PROPAGATION_REQUIRED
methodA(){
doSomeThingA();
methodB();
doSomeThingB();
}
//事務(wù)屬性 PROPAGATION_REQUIRES_NEW
methodB(){
……
}
調(diào)用A方法:
main(){
methodA();
}
相當(dāng)于
main(){
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 {
//釋放資源
}
}
在這里贴届,我把ts1稱為外層事務(wù),ts2稱為內(nèi)層事務(wù)蜡吧。從上面的代碼可以看出,ts2與ts1是兩個獨立的事務(wù)昔善,互不相干元潘。Ts2是否成功并不依賴于 ts1。如果methodA方法在調(diào)用methodB方法后的doSomeThingB方法失敗了君仆,而methodB方法所做的結(jié)果依然被提交翩概。而除了 methodB之外的其它代碼導(dǎo)致的結(jié)果卻被回滾了。使用PROPAGATION_REQUIRES_NEW,需要使用 JtaTransactionManager作為事務(wù)管理器返咱。
(5)PROPAGATION_NOT_SUPPORTED
總是非事務(wù)地執(zhí)行钥庇,并掛起任何存在的事務(wù)。使用PROPAGATION_NOT_SUPPORTED,也需要使用JtaTransactionManager作為事務(wù)管理器洛姑。(代碼示例同上上沐,可同理推出)
(6)PROPAGATION_NEVER
總是非事務(wù)地執(zhí)行,如果存在一個活動事務(wù)楞艾,則拋出異常参咙。
(7)PROPAGATION_NESTED
如果一個活動的事務(wù)存在龄广,則運行在一個嵌套的事務(wù)中. 如果沒有活動事務(wù), 則按TransactionDefinition.PROPAGATION_REQUIRED 屬性執(zhí)行。這是一個嵌套事務(wù),使用JDBC 3.0驅(qū)動時,僅僅支持DataSourceTransactionManager作為事務(wù)管理器蕴侧。需要JDBC 驅(qū)動的java.sql.Savepoint類择同。有一些JTA的事務(wù)管理器實現(xiàn)可能也提供了同樣的功能。使用PROPAGATION_NESTED净宵,還需要把PlatformTransactionManager的nestedTransactionAllowed屬性設(shè)為true;而 nestedTransactionAllowed屬性值默認為false敲才。
//事務(wù)屬性 PROPAGATION_REQUIRED
methodA(){
doSomeThingA();
methodB();
doSomeThingB();
}
//事務(wù)屬性 PROPAGATION_NESTED
methodB(){
……
}
如果單獨調(diào)用methodB方法,則按REQUIRED屬性執(zhí)行择葡。如果調(diào)用methodA方法紧武,相當(dāng)于下面的效果:
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 {
//釋放資源
}
}
當(dāng)methodB方法調(diào)用之前,調(diào)用setSavepoint方法敏储,保存當(dāng)前的狀態(tài)到savepoint阻星。如果methodB方法調(diào)用失敗,則恢復(fù)到之前保存的狀態(tài)已添。但是需要注意的是妥箕,這時的事務(wù)并沒有進行提交,如果后續(xù)的代碼(doSomeThingB()方法)調(diào)用失敗更舞,則回滾包括methodB方法的所有操作畦幢。
嵌套事務(wù)一個非常重要的概念就是內(nèi)層事務(wù)依賴于外層事務(wù)。外層事務(wù)失敗時缆蝉,會回滾內(nèi)層事務(wù)所做的動作宇葱。而內(nèi)層事務(wù)操作失敗并不會引起外層事務(wù)的回滾。
隔離級別
事務(wù)的第二個維度就是隔離級別(isolation level)返奉。隔離級別定義了一個事務(wù)可能受其他并發(fā)事務(wù)影響的程度贝搁。
(1)并發(fā)事務(wù)引起的問題
在典型的應(yīng)用程序中吗氏,多個事務(wù)并發(fā)運行芽偏,經(jīng)常會操作相同的數(shù)據(jù)來完成各自的任務(wù)。并發(fā)雖然是必須的弦讽,但可能會導(dǎo)致一下的問題污尉。
臟讀(Dirty reads)——臟讀
發(fā)生在一個事務(wù)讀取了另一個事務(wù)改寫但尚未提交的數(shù)據(jù)時。如果改寫在稍后被回滾了往产,那么第一個事務(wù)獲取的數(shù)據(jù)就是無效的被碗。
不可重復(fù)讀(Nonrepeatable read)——不可重復(fù)讀
發(fā)生在一個事務(wù)執(zhí)行相同的查詢兩次或兩次以上,但是每次都得到不同的數(shù)據(jù)時仿村。這通常是因為另一個并發(fā)事務(wù)在兩次查詢期間進行了更新锐朴。
幻讀(Phantom read)——幻讀
與不可重復(fù)讀類似。它發(fā)生在一個事務(wù)(T1)讀取了幾行數(shù)據(jù)蔼囊,接著另一個并發(fā)事務(wù)(T2)插入了一些數(shù)據(jù)時焚志。在隨后的查詢中衣迷,第一個事務(wù)(T1)就會發(fā)現(xiàn)多了一些原本不存在的記錄。
不可重復(fù)讀與幻讀的區(qū)別
不可重復(fù)讀的重點是修改:
同樣的條件, 你讀取過的數(shù)據(jù), 再次讀取出來發(fā)現(xiàn)值不一樣了
例如:在事務(wù)1中酱酬,Mary 讀取了自己的工資為1000,操作并沒有完成
con1 = getConnection();
select salary from employee empId ="Mary";
在事務(wù)2中壶谒,這時財務(wù)人員修改了Mary的工資為2000,并提交了事務(wù).
con2 = getConnection();
update employee set salary = 2000;
con2.commit();
在事務(wù)1中,Mary 再次讀取自己的工資時膳沽,工資變?yōu)榱?000
//con1
select salary from employee empId ="Mary";
在一個事務(wù)中前后兩次讀取的結(jié)果并不一致汗菜,導(dǎo)致了不可重復(fù)讀。
幻讀的重點在于新增或者刪除:
同樣的條件, 第1次和第2次讀出來的記錄數(shù)不一樣
例如:目前工資為1000的員工有10人挑社。事務(wù)1,讀取所有工資為1000的員工陨界。
con1 = getConnection();
Select * from employee where salary =1000;
共讀取10條記錄
這時另一個事務(wù)向employee表插入了一條員工記錄,工資也為1000
con2 = getConnection();
Insert into employee(empId,salary) values("Lili",1000);
con2.commit();
事務(wù)1再次讀取所有工資為1000的員工
//con1
select * from employee where salary =1000;
共讀取到了11條記錄痛阻,這就產(chǎn)生了幻像讀普碎。
從總的結(jié)果來看, 似乎不可重復(fù)讀和幻讀都表現(xiàn)為兩次讀取的結(jié)果不一致。但如果你從控制的角度來看, 兩者的區(qū)別就比較大录平。
對于前者, 只需要鎖住滿足條件的記錄麻车。
對于后者, 要鎖住滿足條件及其相近的記錄。
幾種隔離級別
ISOLATION_DEFAULT
使用后端數(shù)據(jù)庫默認的隔離級別
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)的
只讀
事務(wù)的第三個特性是它是否為只讀事務(wù)盯桦。如果事務(wù)只對后端的數(shù)據(jù)庫進行該操作,數(shù)據(jù)庫可以利用事務(wù)的只讀特性來進行一些特定的優(yōu)化渤刃。通過將事務(wù)設(shè)置為只讀拥峦,你就可以給數(shù)據(jù)庫一個機會,讓它應(yīng)用它認為合適的優(yōu)化措施卖子。
事務(wù)超時
為了使應(yīng)用程序很好地運行略号,事務(wù)不能運行太長的時間。因為事務(wù)可能涉及對后端數(shù)據(jù)庫的鎖定,所以長時間的事務(wù)會不必要的占用數(shù)據(jù)庫資源玄柠。事務(wù)超時就是事務(wù)的一個定時器氛琢,在特定時間內(nèi)事務(wù)如果沒有執(zhí)行完畢,那么就會自動回滾随闪,而不是一直等待其結(jié)束阳似。
回滾規(guī)則
事務(wù)五邊形的最后一個方面是一組規(guī)則,這些規(guī)則定義了哪些異常會導(dǎo)致事務(wù)回滾而哪些不會铐伴。默認情況下撮奏,事務(wù)只有遇到運行期異常時才會回滾,而在遇到檢查型異常時不會回滾(這一行為與EJB的回滾行為是一致的)
但是你可以聲明事務(wù)在遇到特定的檢查型異常時像遇到運行期異常那樣回滾当宴。同樣畜吊,你還可以聲明事務(wù)遇到特定的異常不回滾,即使這些異常是運行期異常户矢。
附帶小例子一個
/**
* 1.添加事務(wù)注解
* 使用propagation 指定事務(wù)的傳播行為玲献,即當(dāng)前的事務(wù)方法被另外一個事務(wù)方法調(diào)用時如何使用事務(wù)。
* 默認取值為REQUIRED梯浪,即使用調(diào)用方法的事務(wù)
* REQUIRES_NEW:使用自己的事務(wù)捌年,調(diào)用的事務(wù)方法的事務(wù)被掛起。
*
* 2.使用isolation 指定事務(wù)的隔離級別挂洛,最常用的取值為READ_COMMITTED
* 3.默認情況下 Spring 的聲明式事務(wù)對所有的運行時異常進行回滾礼预,也可以通過對應(yīng)的屬性進行設(shè)置。通常情況下虏劲,默認值即可托酸。
* 4.使用readOnly 指定事務(wù)是否為只讀。 表示這個事務(wù)只讀取數(shù)據(jù)但不更新數(shù)據(jù)柒巫,這樣可以幫助數(shù)據(jù)庫引擎優(yōu)化事務(wù)励堡。若真的是一個只讀取數(shù)據(jù)庫值得方法,應(yīng)設(shè)置readOnly=true
* 5.使用timeOut 指定強制回滾之前事務(wù)可以占用的時間堡掏。
*/
@Transactional(propagation=Propagation.REQUIRES_NEW,
isolation=Isolation.READ_COMMITTED,
noRollbackFor={UserAccountException.class},
readOnly=true, timeout=3)
@Override
public void purchase(String username, String isbn) {
//doSomething.....
}