事務(wù)的四種特性(ACID)
事務(wù)具備 ACID 四種特性,ACID 是 Atomic(原子性)、Consistency(一致性)、Isolation(隔離性)和 Durability(持久性)的英文縮寫(xiě)。
原子性(Atomicity)
事務(wù)最基本的操作單元,要么全部成功,要么全部失敗,不會(huì)結(jié)束在中間某個(gè)環(huán)節(jié)。事務(wù)在執(zhí)行過(guò)程中發(fā)生錯(cuò)誤,會(huì)被回滾到事務(wù)開(kāi)始前的狀態(tài),就像這個(gè)事務(wù)從來(lái)沒(méi)有執(zhí)行過(guò)一樣。
一致性(Consistency)
事務(wù)的一致性指的是在一個(gè)事務(wù)執(zhí)行之前和執(zhí)行之后數(shù)據(jù)庫(kù)都必須處于一致性狀態(tài)。如果事務(wù)成功地完成,那么系統(tǒng)中所有變化將正確地應(yīng)用,系統(tǒng)處于有效狀態(tài)。如果在事務(wù)中出現(xiàn)錯(cuò)誤,那么系統(tǒng)中的所有變化將自動(dòng)地回滾,系統(tǒng)返回到原始狀態(tài)。
隔離性(Isolation)
指的是在并發(fā)環(huán)境中,當(dāng)不同的事務(wù)同時(shí)操縱相同的數(shù)據(jù)時(shí),每個(gè)事務(wù)都有各自的完整數(shù)據(jù)空間。由并發(fā)事務(wù)所做的修改必須與任何其他并發(fā)事務(wù)所做的修改隔離。事務(wù)查看數(shù)據(jù)更新時(shí),數(shù)據(jù)所處的狀態(tài)要么是另一事務(wù)修改它之前的狀態(tài),要么是另一事務(wù)修改它之后的狀態(tài),事務(wù)不會(huì)查看到中間狀態(tài)的數(shù)據(jù)。
持久性(Durability)
指的是只要事務(wù)成功結(jié)束,它對(duì)數(shù)據(jù)庫(kù)所做的更新就必須永久保存下來(lái)。即使發(fā)生系統(tǒng)崩潰,重新啟動(dòng)數(shù)據(jù)庫(kù)系統(tǒng)后,數(shù)據(jù)庫(kù)還能恢復(fù)到事務(wù)成功結(jié)束時(shí)的狀態(tài)。
TransactionDefinition - 五個(gè)隔離級(jí)別
ISOLATION_DEFAULT
這是一個(gè) PlatformTransactionManager 默認(rèn)的隔離級(jí)別,使用數(shù)據(jù)庫(kù)默認(rèn)的事務(wù)隔離級(jí)別。另外四個(gè)與 JDBC 的隔離級(jí)別相對(duì)應(yīng);
ISOLATION_READ_UNCOMMITTED
這是事務(wù)最低的隔離級(jí)別,它充許別外一個(gè)事務(wù)可以看到這個(gè)事務(wù)未提交的數(shù)據(jù)。這種隔離級(jí)別會(huì)產(chǎn)生臟讀,不可重復(fù)讀和幻讀。
ISOLATION_READ_COMMITTED
保證一個(gè)事務(wù)修改的數(shù)據(jù)提交后才能被另外一個(gè)事務(wù)讀取。另外一個(gè)事務(wù)不能讀取該事務(wù)未提交的數(shù)據(jù)。這種事務(wù)隔離級(jí)別可以避免臟讀出現(xiàn),但是可能會(huì)出現(xiàn)不可重復(fù)讀和幻像讀。
ISOLATION_REPEATABLE_READ
這種事務(wù)隔離級(jí)別可以防止臟讀,不可重復(fù)讀。但是可能出現(xiàn)幻讀。它除了保證一個(gè)事務(wù)不能讀取另一個(gè)事務(wù)未提交的數(shù)據(jù)外,還保證了避免下面的情況產(chǎn)生(不可重復(fù)讀)。
ISOLATION_SERIALIZABLE
這是花費(fèi)最高代價(jià)但是最可靠的事務(wù)隔離級(jí)別。事務(wù)被處理為順序執(zhí)行。除了防止臟讀,不可重復(fù)讀外,還避免了幻讀。
事務(wù)的并發(fā)問(wèn)題
Dirty reads - 臟讀
指當(dāng)一個(gè)事務(wù)正字訪問(wèn)數(shù)據(jù),并且對(duì)數(shù)據(jù)進(jìn)行了修改,而這種數(shù)據(jù)還沒(méi)有提交到數(shù)據(jù)庫(kù)中,這時(shí),另外一個(gè)事務(wù)也訪問(wèn)這個(gè)數(shù)據(jù),然后使用了這個(gè)數(shù)據(jù)。因?yàn)檫@個(gè)數(shù)據(jù)還沒(méi)有提交那么另外一個(gè)事務(wù)讀取到的這個(gè)數(shù)據(jù)我們稱之為臟數(shù)據(jù)。依據(jù)臟數(shù)據(jù)所做的操作肯能是不正確的。
non-repeatable reads - 不可重復(fù)讀
指在一個(gè)事務(wù)內(nèi),多次讀同一數(shù)據(jù)。在這個(gè)事務(wù)還沒(méi)有執(zhí)行結(jié)束,另外一個(gè)事務(wù)也訪問(wèn)該同一數(shù)據(jù),那么在第一個(gè)事務(wù)中的兩次讀取數(shù)據(jù)之間,由于第二個(gè)事務(wù)的修改第一個(gè)事務(wù)兩次讀到的數(shù)據(jù)可能是不一樣的,這樣就發(fā)生了在一個(gè)事物內(nèi)兩次連續(xù)讀到的數(shù)據(jù)是不一樣的,這種情況被稱為是不可重復(fù)讀。
phantom reads - 幻讀
這個(gè)和 non-repeatable reads 相似,也是同一個(gè)事務(wù)中多次讀不一致的問(wèn)題。但是 non-repeatable reads 的不一致是因?yàn)樗〉臄?shù)據(jù)集被改變了(比如 total 的數(shù)據(jù)),但是 phantom reads 所要讀的數(shù)據(jù)的不一致卻不是他所要讀的數(shù)據(jù)集改變,而是他的條件數(shù)據(jù)集改變。比如 Select account.id where account.name="ppgogo*",第一次讀取了 6 個(gè)符合條件的 id,第二次讀取的時(shí)候,由于事務(wù) b 把一個(gè)帳號(hào)的名字由"dd"改成"ppgogo1",結(jié)果取出來(lái)了 7 個(gè)數(shù)據(jù)。概括:一個(gè)事務(wù)先后讀取一個(gè)范圍的記錄,但兩次讀取的紀(jì)錄數(shù)不同,我們稱之為幻象讀。
TransactionDefinition - 七個(gè)事務(wù)傳播行為:
PROPAGATION_REQUIRED
如果存在一個(gè)事務(wù),則支持當(dāng)前事務(wù)。如果沒(méi)有事務(wù)則開(kāi)啟一個(gè)新的事務(wù)。
// 事務(wù)屬性 PROPAGATION_REQUIRED
methodA {
……
methodB();
……
}
// 事務(wù)屬性 PROPAGATION_REQUIRED
methodB{……}
使用 spring 聲明式事務(wù),spring 使用 AOP 來(lái)支持聲明式事務(wù),會(huì)根據(jù)事務(wù)屬性,自動(dòng)在方法調(diào)用之前決定是否開(kāi)啟一個(gè)事務(wù),并在方法執(zhí)行之后決定事務(wù)提交或回滾事務(wù)。
單獨(dú)調(diào)用 methodB 方法相當(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)用都獲得到一個(gè)相同的連接。在調(diào)用 methodB 時(shí),沒(méi)有一個(gè)存在的事務(wù),所以獲得一個(gè)新的連接,開(kāi)啟了一個(gè)新的事務(wù)。
單獨(dú)調(diào)用 MethodA 時(shí),在 MethodA 內(nèi)又會(huì)調(diào)用 MethodB,執(zhí)行效果相當(dāng)于:
public static void main(String[] args) {
Connection con = null;
try {
con = getConnection();
methodA();
con.commit();
} catch (RuntimeException ex) {
con.rollback();
} finally {
closeCon();
}
}
調(diào)用 MethodA 時(shí),環(huán)境中沒(méi)有事務(wù),所以開(kāi)啟一個(gè)新的事務(wù).當(dāng)在 MethodA 中調(diào)用 MethodB 時(shí),環(huán)境中已經(jīng)有了一個(gè)事務(wù),所以 methodB 就加入當(dāng)前事務(wù)。
PROPAGATION_SUPPORTS
如果存在一個(gè)事務(wù),支持當(dāng)前事務(wù)。如果沒(méi)有事務(wù),則非事務(wù)的執(zhí)行。但是對(duì)于事務(wù)同步的事務(wù)管理器,PROPAGATION_SUPPORTS 與不使用事務(wù)有少許不同。
// 事務(wù)屬性 PROPAGATION_REQUIRED
methodA {
……
methodB();
……
}
// 事務(wù)屬性 PROPAGATION_SUPPORTS
methodB{……}
單純的調(diào)用 methodB 時(shí),methodB 方法是非事務(wù)的執(zhí)行的。當(dāng)調(diào)用 methdA 時(shí),methodB 則加入了 methodA 的事務(wù)中,事務(wù)地執(zhí)行。
PROPAGATION_MANDATORY
如果已經(jīng)存在一個(gè)事務(wù),支持當(dāng)前事務(wù)。如果沒(méi)有一個(gè)活動(dòng)的事務(wù),則拋出異常。
// 事務(wù)屬性 PROPAGATION_REQUIRED
methodA {
……
methodB();
……
}
// 事務(wù)屬性 PROPAGATION_MANDATORY
methodB{……}
當(dāng)單獨(dú)調(diào)用 methodB 時(shí),因?yàn)楫?dāng)前沒(méi)有一個(gè)活動(dòng)的事務(wù),則會(huì)拋出異常 throw new IllegalTransactionStateException("Transaction propagation 'mandatory' but no existing transaction found");當(dāng)調(diào)用 methodA 時(shí),methodB 則加入到 methodA 的事務(wù)中,事務(wù)地執(zhí)行。
PROPAGATION_REQUIRES_NEW
總是開(kāi)啟一個(gè)新的事務(wù)。如果一個(gè)事務(wù)已經(jīng)存在,則將這個(gè)存在的事務(wù)掛起。
// 事務(wù)屬性 PROPAGATION_REQUIRED
methodA {
……
methodB();
……
}
// 事務(wù)屬性 PROPAGATION_REQUIRES_NEW
methodB{……}
調(diào)用 methodA 時(shí),相當(dāng)于:
public static void main(String[] args) {
TransactionManager tm = null;
try {
tm = getTransactionManager(); //獲得一個(gè)JTA事務(wù)管理器
tm.begin(); // 開(kāi)啟一個(gè)新的事務(wù)
Transaction ts1 = tm.getTransaction();
doSomeThing();
tm.suspend(); //掛起當(dāng)前事務(wù)
try {
tm.begin(); // 重新開(kāi)啟第二個(gè)事務(wù)
Transaction ts2 = tm.getTransaction();
methodB();
ts2.commit(); // 提交第二個(gè)事務(wù)
} catch (RunTimeException ex) {
ts2.rollback(); //回滾第二個(gè)事務(wù)
} finally {
//釋放資源
}
tm.resume(ts1); // methodB執(zhí)行完后,復(fù)恢第一個(gè)事務(wù)
doSomeThingB();
ts1.commit(); // 提交第一個(gè)事務(wù)
} catch (RunTimeException ex) {
ts1.rollback(); // 回滾第一個(gè)事務(wù)
} finally {
//釋放資源
}
}
在這里,我把 ts1 稱為外層事務(wù),ts2 稱為內(nèi)層事務(wù)。從上面的代碼可以看出,ts2 與 ts1 是兩個(gè)獨(dú)立的事務(wù),互不相干。Ts2 是否成功并不依賴于 ts1。如果 methodA 方法在調(diào)用 methodB 方法后的 doSomeThingB 方法失敗了,而 methodB 方法所做的結(jié)果依然被提交。而除了 methodB 之外的其它代碼導(dǎo)致的結(jié)果卻被回滾了。使用 PROPAGATION_REQUIRES_NEW,需要使用 JtaTransactionManager 作為事務(wù)管理器。
PROPAGATION_NOT_SUPPORTED
總是非事務(wù)地執(zhí)行,并掛起任何存在的事務(wù)。使用 PROPAGATION_NOT_SUPPORTED,也需要使用 JtaTransactionManager 作為事務(wù)管理器。(代碼示例同上,可同理推出)
PROPAGATION_NEVER
總是非事務(wù)地執(zhí)行,如果存在一個(gè)活動(dòng)事務(wù),則拋出異常。
PROPAGATION_NESTED
如果一個(gè)活動(dòng)的事務(wù)存在,則運(yùn)行在一個(gè)嵌套的事務(wù)中。如果沒(méi)有活動(dòng)事務(wù),則按 TransactionDefinition.PROPAGATION_REQUIRED 屬性執(zhí)行。這是一個(gè)嵌套事務(wù),使用 JDBC 3.0 驅(qū)動(dòng)時(shí),僅僅支持 DataSourceTransactionManager 作為事務(wù)管理器。需要 JDBC 驅(qū)動(dòng)的 java.sql.Savepoint 類。有一些 JTA 的事務(wù)管理器實(shí)現(xiàn)可能也提供了同樣的功能。使用 PROPAGATION_NESTED,還需要把 PlatformTransactionManager 的 nestedTransactionAllowed 屬性設(shè)為 true,而 nestedTransactionAllowed 屬性值默認(rèn)為 false。
// 事務(wù)屬性 PROPAGATION_REQUIRED
methodA {
……
methodB();
……
}
// 事務(wù)屬性 PROPAGATION_NESTED
methodB{……}
如果單獨(dú)調(diào)用 methodB 方法,則按 REQUIRED 屬性執(zhí)行。如果調(diào)用methodA方法,相當(dāng)于下面的效果:
public static void main(String[] args) {
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)。但是需要注意的是,這時(shí)的事務(wù)并沒(méi)有進(jìn)行提交,如果后續(xù)的代碼(doSomeThingB()方法)調(diào)用失敗,則回滾包括 methodB 方法的所有操作。
嵌套事務(wù)一個(gè)非常重要的概念就是內(nèi)層事務(wù)依賴于外層事務(wù)。外層事務(wù)失敗時(shí),會(huì)回滾內(nèi)層事務(wù)所做的動(dòng)作。而內(nèi)層事務(wù)操作失敗并不會(huì)引起外層事務(wù)的回滾。
PROPAGATION_NESTED 與 PROPAGATION_REQUIRES_NEW 的區(qū)別:它們非常類似,都像一個(gè)嵌套事務(wù),如果不存在一個(gè)活動(dòng)的事務(wù),都會(huì)開(kāi)啟一個(gè)新的事務(wù)。使用 PROPAGATION_REQUIRES_NEW 時(shí),內(nèi)層事務(wù)與外層事務(wù)就像兩個(gè)獨(dú)立的事務(wù)一樣,一旦內(nèi)層事務(wù)進(jìn)行了提交后,外層事務(wù)不能對(duì)其進(jìn)行回滾。兩個(gè)事務(wù)互不影響。兩個(gè)事務(wù)不是一個(gè)真正的嵌套事務(wù)。同時(shí)它需要 JTA 事務(wù)管理器的支持。
使用 PROPAGATION_NESTED 時(shí),外層事務(wù)的回滾可以引起內(nèi)層事務(wù)的回滾。而內(nèi)層事務(wù)的異常并不會(huì)導(dǎo)致外層事務(wù)的回滾,它是一個(gè)真正的嵌套事務(wù)。DataSourceTransactionManager 使用 savepoint 支持 PROPAGATION_NESTED 時(shí),需要 JDBC 3.0 以上驅(qū)動(dòng)及 1.4 以上的 JDK 版本支持。其它的 JTA TrasactionManager 實(shí)現(xiàn)可能有不同的支持方式。
PROPAGATION_REQUIRES_NEW 啟動(dòng)一個(gè)新的,不依賴于環(huán)境的“內(nèi)部”事務(wù)。這個(gè)事務(wù)將被完全 commited 或 rolled back 而不依賴于外部事務(wù),它擁有自己的隔離范圍,自己的鎖,等等。當(dāng)內(nèi)部事務(wù)開(kāi)始執(zhí)行時(shí),外部事務(wù)將被掛起,內(nèi)務(wù)事務(wù)結(jié)束時(shí),外部事務(wù)將繼續(xù)執(zhí)行。
另一方面,PROPAGATION_NESTED 開(kāi)始一個(gè)“嵌套的”事務(wù),它是已經(jīng)存在事務(wù)的一個(gè)真正的子事務(wù)。嵌套事務(wù)開(kāi)始執(zhí)行時(shí),它將取得一個(gè) savepoint。如果這個(gè)嵌套事務(wù)失敗,我們將回滾到此 savepoint。潛套事務(wù)是外部事務(wù)的一部分,只有外部事務(wù)結(jié)束后它才會(huì)被提交。
由此可見(jiàn),PROPAGATION_REQUIRES_NEW 和 PROPAGATION_NESTED 的最大區(qū)別在于,PROPAGATION_REQUIRES_NEW 完全是一個(gè)新的事務(wù),而 PROPAGATION_NESTED 則是外部事務(wù)的子事務(wù),如果外部事務(wù) commit,嵌套事務(wù)也會(huì)被 commit,這個(gè)規(guī)則同樣適用于 roll back。
PROPAGATION_REQUIRED 應(yīng)該是我們首先的事務(wù)傳播行為。它能夠滿足我們大多數(shù)的事務(wù)需求。
spring 事務(wù)的四種實(shí)現(xiàn)方式
編程式事務(wù)
編程式事務(wù)管理對(duì)基于 POJO 的應(yīng)用來(lái)說(shuō)是唯一選擇。我們需要在代碼中調(diào)用 beginTransaction()、commit()、rollback() 等事務(wù)管理相關(guān)的方法,這就是編程式事務(wù)管理。基本上廢棄掉了。