Spring事務(wù)處理


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_REQUIREDPROPAGATION_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è)置 transactionManagernestedTransactionAllowed 屬性為 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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末活翩,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子翻伺,更是在濱河造成了極大的恐慌材泄,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,539評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件吨岭,死亡現(xiàn)場(chǎng)離奇詭異拉宗,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)辣辫,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,594評(píng)論 3 396
  • 文/潘曉璐 我一進(jìn)店門旦事,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人急灭,你說我怎么就攤上這事姐浮。” “怎么了葬馋?”我有些...
    開封第一講書人閱讀 165,871評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵卖鲤,是天一觀的道長肾扰。 經(jīng)常有香客問我,道長蛋逾,這世上最難降的妖魔是什么集晚? 我笑而不...
    開封第一講書人閱讀 58,963評(píng)論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮区匣,結(jié)果婚禮上偷拔,老公的妹妹穿的比我還像新娘。我一直安慰自己亏钩,他們只是感情好莲绰,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,984評(píng)論 6 393
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著姑丑,像睡著了一般蛤签。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上彻坛,一...
    開封第一講書人閱讀 51,763評(píng)論 1 307
  • 那天顷啼,我揣著相機(jī)與錄音,去河邊找鬼昌屉。 笑死钙蒙,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的间驮。 我是一名探鬼主播躬厌,決...
    沈念sama閱讀 40,468評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼竞帽!你這毒婦竟也來了扛施?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,357評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤屹篓,失蹤者是張志新(化名)和其女友劉穎疙渣,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體堆巧,經(jīng)...
    沈念sama閱讀 45,850評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡妄荔,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,002評(píng)論 3 338
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了谍肤。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片啦租。...
    茶點(diǎn)故事閱讀 40,144評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖荒揣,靈堂內(nèi)的尸體忽然破棺而出篷角,到底是詐尸還是另有隱情,我是刑警寧澤系任,帶...
    沈念sama閱讀 35,823評(píng)論 5 346
  • 正文 年R本政府宣布恳蹲,位于F島的核電站虐块,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏阱缓。R本人自食惡果不足惜非凌,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,483評(píng)論 3 331
  • 文/蒙蒙 一举农、第九天 我趴在偏房一處隱蔽的房頂上張望荆针。 院中可真熱鬧,春花似錦颁糟、人聲如沸航背。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,026評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽玖媚。三九已至,卻和暖如春婚脱,著一層夾襖步出監(jiān)牢的瞬間今魔,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,150評(píng)論 1 272
  • 我被黑心中介騙來泰國打工障贸, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留错森,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,415評(píng)論 3 373
  • 正文 我出身青樓篮洁,卻偏偏與公主長得像涩维,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子袁波,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,092評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容