1. 事務(wù)基礎(chǔ)
1.1 什么是事務(wù)
所謂事務(wù)就是用戶定義的一個(gè)數(shù)據(jù)庫操作序列鸡典,這些操作要么全做挣菲,要么全不做贮喧,是一個(gè)不可分割的工作單位顾瞪。
1.2 事務(wù)的特性
原子性:事務(wù)以原子為工作單位舔庶。對(duì)于數(shù)據(jù)修改,要么全做陈醒,要么全不做惕橙。
一致性:事務(wù)在完成時(shí),必須使所有的數(shù)據(jù)都保持一致狀態(tài)钉跷,以保持所有數(shù)據(jù)的完整性弥鹦。
隔離性:一個(gè)事務(wù)的執(zhí)行不能被其他事務(wù)干擾,寄一個(gè)事務(wù)的內(nèi)部操作以及使用的數(shù)據(jù)對(duì)其他并發(fā)事務(wù)是隔離的。
持續(xù)性:一個(gè)事務(wù)一旦提交彬坏,它對(duì)數(shù)據(jù)庫中數(shù)據(jù)的改變是永久性的朦促。
1.3 事務(wù)并發(fā)操作帶來的問題
1.3.1 丟失修改
兩個(gè)事務(wù)T1和T2讀入同一個(gè)數(shù)據(jù)并修改,T2提交的結(jié)果破壞了T1提交的結(jié)果栓始,導(dǎo)致T1的修改被丟失务冕。
1.3.2 不可重復(fù)讀
事務(wù)T1讀取數(shù)據(jù)后,事務(wù)T2執(zhí)行更新操作幻赚,使得T1無法再現(xiàn)前一次讀到的結(jié)果禀忆。
事務(wù)T1讀取某一數(shù)據(jù)后,T2對(duì)其做了修改落恼,當(dāng)T1再次讀該數(shù)據(jù)后箩退,得到與前一不同的值
1.3.3 讀臟數(shù)據(jù)
T1修改某一個(gè)數(shù)據(jù)并將其寫會(huì)磁盤,事務(wù)T2讀到T1修改之后的數(shù)據(jù)佳谦,這時(shí)T1由于某種原因被撤銷戴涝,數(shù)據(jù)恢復(fù)到原來的值,T1讀到的數(shù)據(jù)為臟數(shù)據(jù)钻蔑。
1.3.4 幻讀
按一定條件從數(shù)據(jù)庫中讀取了某些記錄后喊括,T2刪除了其中部分記錄,當(dāng)T1再次按相同條件讀取數(shù)據(jù)時(shí)矢棚,發(fā)現(xiàn)某些記錄消失郑什。
T1按一定條件從數(shù)據(jù)庫中讀取某些數(shù)據(jù)記錄后,T2插入了一些記錄蒲肋,當(dāng)T1再次按相同條件讀取數(shù)據(jù)時(shí)蘑拯,發(fā)現(xiàn)多了一些記錄
1.4 事務(wù)管理器PlatformTransactionManager
Spring為不同的持久化框架提供了不同的PlatformTransacetionManager
事務(wù) | 說明 |
---|---|
org.springframework.jdbc.datasource.DataSourceTranasactionManager | 使用Spring JDBC或Mybatis進(jìn)行持久化數(shù)據(jù)時(shí)使用 |
org.springframework.hibernate3.HibernateTransactionManager | 使用Hibernate3.0版本進(jìn)行持久化數(shù)據(jù)時(shí)使用 |
org.springframework.orm.jpa.JpaTransactionManager | 使用JPA進(jìn)行持久化時(shí)使用 |
org.springframework.jdo,JdoTransactionManager | 當(dāng)持久化機(jī)制是Jdo時(shí)使用 |
org.springframework.transaction.jta.JtaTransactionManager | 使用一個(gè)JTA實(shí)現(xiàn)來管理事務(wù),在一個(gè)事務(wù)跨越多個(gè)資源時(shí)使用 |
1.5 TransacrionDefinition事務(wù)定義信息(隔離兜粘,傳播申窘,超時(shí),只讀)
事務(wù)設(shè)置隔離級(jí)別可以防止并發(fā)導(dǎo)致的問題
事務(wù)隔離級(jí)別
隔離級(jí)別 | 含義 |
---|---|
DEFAULT | 使用后端數(shù)據(jù)庫默認(rèn)的隔離級(jí)別(Spring中的選擇項(xiàng)) |
READ_UNCOMMITED | 允許你讀取還未提交的改變了的數(shù)據(jù)孔轴√攴ǎ可能導(dǎo)致臟、幻路鹰、不可重復(fù)讀 |
READ_COMMITED | 允許在并發(fā)事件已經(jīng)提交后讀取贷洲,可防止臟讀,但幻讀和不可重復(fù)讀仍可發(fā)生 |
REPEATABLE_READ | 對(duì)相同字段的多次讀取是一致的晋柱,除非數(shù)據(jù)唄事務(wù)本身改變优构,可防止臟、不可重復(fù)讀雁竞,但幻讀仍可能發(fā)生 |
SERIALIZABLE | 完全服從ACID的隔離級(jí)別钦椭,確保不發(fā)生臟,幻,不可重復(fù)讀彪腔,這在所有的隔離級(jí)別中是最慢的侥锦,它是典型的通過完全鎖定在事務(wù)中涉及的數(shù)據(jù)表來完成的 |
如果選擇DEFAULT則是數(shù)據(jù)庫默認(rèn)的隔離級(jí)別。
mysql 默認(rèn)REPEATABLE_READ
oracle 默認(rèn)READ_COMMITTED
1.6 事務(wù)傳播行為
PROPAGATION_REQUIRED
-- 支持當(dāng)前事務(wù)德挣,如果當(dāng)前沒有事務(wù)捎拯,就新建一個(gè)事務(wù)。這是最常見的選擇盲厌。
PROPAGATION_SUPPORTS
-- 支持當(dāng)前事務(wù)署照,如果當(dāng)前沒有事務(wù),就以非事務(wù)方式執(zhí)行吗浩。
PROPAGATION_MANDATORY
-- 支持當(dāng)前事務(wù)建芙,如果當(dāng)前沒有事務(wù),就拋出異常懂扼。
PROPAGATION_REQUIRES_NEW
-- 新建事務(wù)禁荸,如果當(dāng)前存在事務(wù),把當(dāng)前事務(wù)掛起阀湿。
PROPAGATION_NOT_SUPPORTED
-- 以非事務(wù)方式執(zhí)行操作赶熟,如果當(dāng)前存在事務(wù),就把當(dāng)前事務(wù)掛起陷嘴。
PROPAGATION_NEVER
-- 以非事務(wù)方式執(zhí)行映砖,如果當(dāng)前存在事務(wù),則拋出異常灾挨。
PROPAGATION_NESTED
-- 如果當(dāng)前存在事務(wù)邑退,則在嵌套事務(wù)內(nèi)執(zhí)行。如果當(dāng)前沒有事務(wù)劳澄,則進(jìn)行與PROPAGATION_REQUIRED
類似的操作地技。
前六個(gè)策略類似于EJB CMT,第七個(gè)(PROPAGATION_NESTED
)是Spring所提供的一個(gè)特殊變量秒拔。
它要求事務(wù)管理器或者使用JDBC 3.0 Savepoint API提供嵌套事務(wù)行為(如Spring的DataSourceTransactionManager
)
PROPAGATION_REQUIRED
兩個(gè)事務(wù)一定一起執(zhí)行
PROPAGATION_REQUIRES_NEW
兩個(gè)事務(wù)一定不一起執(zhí)行
PROPAGATION_NESTED
設(shè)置savepoint
PROPAGATION_REQUIRES_NEW
啟動(dòng)一個(gè)新的莫矗,不依賴于環(huán)境的“內(nèi)部”事務(wù)。這個(gè)事務(wù)將被完全commited或rolled back而不依賴于外部事務(wù)砂缩,它擁有自己的隔離范圍作谚,自己的鎖等等。當(dāng)內(nèi)部事務(wù)開始執(zhí)行時(shí)梯轻,外部事務(wù)將被掛起食磕,內(nèi)部事務(wù)結(jié)束后,外部事務(wù)將繼續(xù)執(zhí)行喳挑。
另一方面,PROPAGATION_NESTED
開始一個(gè)“嵌套的”事務(wù),它是已經(jīng)存在事務(wù)的一個(gè)真正的子事務(wù)伊诵,嵌套事務(wù)開始執(zhí)行時(shí)单绑,它將取得一個(gè)savepoint。如果這個(gè)嵌套事務(wù)失敗曹宴,我們將回滾到此savepoint搂橙。嵌套事務(wù)是外部事務(wù)的一部分,只有外部事務(wù)結(jié)束后它才會(huì)被提交笛坦。
二者最大區(qū)別是:PROPAGATION_REQUIRES_NEW完全是一個(gè)新的事務(wù)区转,PROPAGATION_NESTED
是一個(gè)已存在的外部事務(wù)的子事務(wù),受外部事務(wù)影響版扩,如果外部事務(wù)commit或rollback废离,嵌套事務(wù)也會(huì)被commit或rollback。
外部事務(wù)利用嵌套事務(wù)的savepoint特性
ServiceA {
/**
*事務(wù)屬性配置為PROPAGATION_REQUIRES
*/
void methodA() {
ServiceB.methodB();
}
}
ServiceB {
/**
*事務(wù)屬性配置為PROPAGATION_REQUIRES_NEW
*/
void methodB() {
}
}
這種情況下, 因?yàn)?ServiceB#methodB 的事務(wù)屬性為 PROPAGATION_REQUIRES_NEW
, 所以兩者不會(huì)發(fā)生任何關(guān)系, ServiceA#methodA 和 ServiceB#methodB 不會(huì)因?yàn)閷?duì)方的執(zhí)行情況而影響事務(wù)的結(jié)果, 因?yàn)樗鼈兏揪褪莾蓚€(gè)事務(wù), 在 ServiceB#methodB 執(zhí)行時(shí) ServiceA#methodA 的事務(wù)已經(jīng)掛起了礁芦。
ServiceA {
/**
* 事務(wù)屬性配置為 PROPAGATION_REQUIRED
*/
void methodA() {
ServiceB.methodB();
}
}
ServiceB {
/**
* 事務(wù)屬性配置為 PROPAGATION_NESTED
*/
void methodB() {
}
}
現(xiàn)在的情況就變得比較復(fù)雜了, ServiceB#methodB 的事務(wù)屬性被配置為 PROPAGATION_NESTED
, 此時(shí)兩者之間又將如何協(xié)作呢? 從 Juergen Hoeller 的原話中我們可以找到答案, ServiceB#methodB 如果 rollback, 那么內(nèi)部事務(wù)(即 ServiceB#methodB) 將回滾到它執(zhí)行前的 SavePoint(注意, 這是本文中第一次提到它, 潛套事務(wù)中最核心的概念), 而外部事務(wù)(即 ServiceA#methodA) 可以有以下兩種處理方式:
1. 改寫 ServiceA 如下
ServiceA {
/**
* 事務(wù)屬性配置為 PROPAGATION_REQUIRED
*/
void methodA() {
try {
ServiceB.methodB();
} catch (SomeException) {
// 執(zhí)行其他業(yè)務(wù), 如 ServiceC.methodC();
}
}
}
這種方式也是潛套事務(wù)最有價(jià)值的地方, 它起到了分支執(zhí)行的效果, 如果 ServiceB.methodB 失敗, 那么執(zhí)行 ServiceC.methodC(), 而 ServiceB.methodB 已經(jīng)回滾到它執(zhí)行之前的 SavePoint, 所以不會(huì)產(chǎn)生臟數(shù)據(jù)(相當(dāng)于此方法從未執(zhí)行過), 這種特性可以用在某些特殊的業(yè)務(wù)中, 而 PROPAGATION_REQUIRED
和 PROPAGATION_REQUIRES_NEW
都沒有辦法做到這一點(diǎn)蜻韭。
2.代碼不做任何修改
那么如果內(nèi)部事務(wù)(即 ServiceB#methodB) rollback, 那么首先 ServiceB.methodB 回滾到它執(zhí)行之前的 SavePoint(在任何情況下都會(huì)如此), 外部事務(wù)(即 ServiceA#methodA) 將根據(jù)具體的配置決定自己是 commit 還是 rollback (+MyCheckedException)。
2.Spring事務(wù)代理
攔截器先創(chuàng)建一個(gè)TransactionInfo 對(duì)象:
TransactionInfo txInfo = new TransactionInfo(txAttr, method);
只要被調(diào)用的方法設(shè)置了事務(wù)屬性(txAttr),不管是什么屬性都會(huì)調(diào)用:
txInfo.newTransactionStatus(this.transactionManager.getTransaction(txAttr));
根據(jù)該方法的事務(wù)屬性(definition )的不同,this.transactionManager.getTransaction(txAttr)
的返回值會(huì)有所不同(代碼見AbstractPlatformTransactionManager
),具體為以下幾種情況:
1.當(dāng)前沒有事務(wù)時(shí)(即以下代碼中的((HibernateTransactionObject) transaction).hasTransaction()
返回false),會(huì)返回以下幾種:
//檢查新事務(wù)的定義設(shè)置
if (definition.getTimeout() < TransactionDefinition.TIMEOUT_DEFAULT) {
throw new InvalidTimeoutException("Invalid transaction timeout", definition.getTimeout());
}
// 找不到現(xiàn)有的事務(wù) - >檢查傳播行為柿扣,以了解如何行為肖方。
if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_MANDATORY) {
throw new IllegalTransactionStateException(
"Transaction propagation 'mandatory' but no existing transaction found");
}
else if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRED ||
definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRES_NEW ||
definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NESTED) {
if (debugEnabled) {
logger.debug("Creating new transaction with name [" + definition.getName() + "]");
}
doBegin(transaction, definition);
boolean newSynchronization = (this.transactionSynchronization != SYNCHRONIZATION_NEVER);
return newTransactionStatus(definition, transaction, true, newSynchronization, debugEnabled, null);
}
else {
// 創(chuàng)建“空”事務(wù):沒有實(shí)際的事務(wù),但潛在的同步
boolean newSynchronization = (this.transactionSynchronization == SYNCHRONIZATION_ALWAYS);
return newTransactionStatus(definition, null, false, newSynchronization, debugEnabled, null);
}
2.當(dāng)前有事務(wù)時(shí)
/**
* 為現(xiàn)有事務(wù)創(chuàng)建一個(gè)TransactionStatus
*/
private TransactionStatus handleExistingTransaction(
TransactionDefinition definition, Object transaction, boolean debugEnabled)
throws TransactionException {
...
if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NESTED) {
if (!isNestedTransactionAllowed()) {
throw new NestedTransactionNotSupportedException(
"Transaction manager does not allow nested transactions by default - " +
"specify 'nestedTransactionAllowed' property with value 'true'");
}
if (debugEnabled) {
logger.debug("Creating nested transaction with name [" + definition.getName() + "]");
}
if (useSavepointForNestedTransaction()) {
// 在現(xiàn)有的Spring管理事務(wù)中創(chuàng)建保存點(diǎn)
// 通過TransactionStatus實(shí)現(xiàn)的SavepointManager API
// 通常使用JDBC 3.0保存點(diǎn)未状。不激活Spring同步
DefaultTransactionStatus status =
newTransactionStatus(definition, transaction, false, false, debugEnabled, null);
status.createAndHoldSavepoint();
return status;
}
else {
//嵌套事務(wù)通過嵌套的開始和提交/回滾調(diào)用
// 通常僅適用于JTA:Spring同步可能會(huì)在此處激活
// 在預(yù)先存在的JTA事務(wù)中俯画。
doBegin(transaction, definition);
boolean newSynchronization = (this.transactionSynchronization != SYNCHRONIZATION_NEVER);
return newTransactionStatus(definition, transaction, true, newSynchronization, debugEnabled, null);
}
}
}
最后,txInfo被綁定到當(dāng)前線程上作為當(dāng)前事務(wù):
txInfo.bindToThread()
然后司草,調(diào)用實(shí)際的目標(biāo)類的方法并捕捉異常:
try {
// This is an around advice.
// Invoke the next interceptor in the chain.
// This will normally result in a target object being invoked.
retVal = invocation.proceed();
}
catch (Throwable ex) {
// target invocation exception
doCloseTransactionAfterThrowing(txInfo, ex);
throw ex;
}
finally {
doFinally(txInfo);
}
doCommitTransactionAfterReturning(txInfo);
return retVal;
}
另外一點(diǎn),TransactionInfo的newTransactionStatus調(diào)用時(shí)如果參數(shù)的不是null,TransactionInfo.hasTransaction()方法返回true;
重要提示:
在spring中創(chuàng)建的事務(wù)代理類并是目標(biāo)類的超類,只是一個(gè)實(shí)現(xiàn)這目標(biāo)類接口的類,該類會(huì)調(diào)用目標(biāo)類的方法,所在如果一個(gè)目標(biāo)類中的方法調(diào)用自身的另一個(gè)事務(wù)方法,另一個(gè)方法只是作為普通方法來調(diào)用,并不會(huì)加入事務(wù)機(jī)制
如果要使用PROPAGATION_NESTED
1.設(shè)置 transactionManager
的 nestedTransactionAllowed
屬性為 true, 注意, 此屬性默認(rèn)為 false
再看 AbstractTransactionStatus的createAndHoldSavepoint() 方法
/**
* Create a savepoint and hold it for the transaction.
* @throws org.springframework.transaction.NestedTransactionNotSupportedException
* if the underlying transaction does not support savepoints
*/
public void createAndHoldSavepoint() throws TransactionException {
setSavepoint(getSavepointManager().createSavepoint());
}
可以看到 Savepoint 是 SavepointManager.createSavepoint 實(shí)現(xiàn)的, 再看 SavepointManager 的層次結(jié)構(gòu), 發(fā)現(xiàn) 其 Template 實(shí)現(xiàn)是 JdbcTransactionObjectSupport, 常用的 DatasourceTransactionManager, HibernateTransactionManager 中的 TransactonObject 都是它的子類
JdbcTransactionObjectSupport 告訴我們必須要滿足兩個(gè)條件才能 createSavepoint :
2. java.sql.Savepoint 必須存在, 即 jdk 版本要 1.4+
3.Connection.getMetaData().supportsSavepoints() 必須為 true, 即 jdbc drive 必須支持 JDBC 3.0 確保以上條件都滿足后, 你就可以嘗試使用 PROPAGATION_NESTED
了
參考資料:
1.http://www.iteye.com/topic/35907
2.Spring源代碼https://docs.spring.io/spring/docs/current/javadoc-api/
3.百度百科https://baike.baidu.com/item/數(shù)據(jù)庫事務(wù)/9744607?fr=aladdin