溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點(diǎn)擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

Spring事務(wù)

發(fā)布時(shí)間:2020-04-06 06:18:03 來源:網(wǎng)絡(luò) 閱讀:285 作者:winters1224 欄目:軟件技術(shù)

spring事務(wù)基本配置

參見:http://www.cnblogs.com/leiOOlei/p/3725911.html

spring事務(wù)傳播機(jī)制

參見:http://www.cnblogs.com/aurawing/articles/1887030.html

簡單說一下new和nested的區(qū)別。
使用new的時(shí)候,外層事務(wù)的提交或回滾,與new的事務(wù)沒有關(guān)系。而使用nested時(shí),內(nèi)層事務(wù)最終是提交還是回滾,需要依賴于外層事務(wù)。參見下表。

事務(wù)傳播配置 外層事務(wù)(set a=1) 內(nèi)層事務(wù)(set b=2) 最終結(jié)果
new 提交 提交 a=1 && b=2
new 提交 回滾 a=1
new 回滾 提交 b=2
new 回滾 回滾 什么都不變
nested 提交 提交 a=1 && b=2
nested 提交 回滾 a=1(這種情況需要增加一個(gè)配置:<property name="globalRollbackOnParticipationFailure" value="false" />
nested 回滾 提交 什么都不變
nested 回滾 回滾 什么都不變

spring事務(wù)隔離機(jī)制

參見:Isolation Level(事務(wù)隔離等級(jí)):

  1. Serializable:最嚴(yán)格的級(jí)別,事務(wù)串行執(zhí)行,資源消耗最大;
  2. REPEATABLE READ:保證了一個(gè)事務(wù)不會(huì)修改已經(jīng)由另一個(gè)事務(wù)讀取但未提交(回滾)的數(shù)據(jù)。避免了“臟讀取”和“不可重復(fù)讀取”的情況,但是帶來了更多的性能損失。
  3. READ COMMITTED:大多數(shù)主流數(shù)據(jù)庫的默認(rèn)事務(wù)等級(jí),保證了一個(gè)事務(wù)不會(huì)讀到另一個(gè)并行事務(wù)已修改但未提交的數(shù)據(jù),避免了“臟讀取”。該級(jí)別適用于大多數(shù)系統(tǒng)。
  4. Read Uncommitted:保證了讀取過程中不會(huì)讀取到非法數(shù)據(jù)。隔離級(jí)別在于處理多事務(wù)的并發(fā)問題。

我們知道并行可以提高數(shù)據(jù)庫的吞吐量和效率,但是并不是所有的并發(fā)事務(wù)都可以并發(fā)運(yùn)行,這需要查看數(shù)據(jù)庫教材的可串行化條件判斷了。

這里就不闡述了。
我們首先說并發(fā)中可能發(fā)生的3中不討人喜歡的事情

  1. Dirty reads--讀臟數(shù)據(jù)。也就是說,比如事務(wù)A的未提交(還依然緩存)的數(shù)據(jù)被事務(wù)B讀走,如果事務(wù)A失敗回滾,會(huì)導(dǎo)致事務(wù)B所讀取的的數(shù)據(jù)是錯(cuò)誤的。
  2. non-repeatable reads--數(shù)據(jù)不可重復(fù)讀。比如事務(wù)A中兩處讀取數(shù)據(jù)-total-的值。在第一讀的時(shí)候,total是100,然后事務(wù)B就把total的數(shù)據(jù)改成200,事務(wù)A再讀一次,結(jié)果就發(fā)現(xiàn),total竟然就變成200了,造成事務(wù)A數(shù)據(jù)混亂。
  3. phantom reads--幻象讀數(shù)據(jù),這個(gè)和non-repeatable reads相似,也是同一個(gè)事務(wù)中多次讀不一致的問題。但是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é)果取出來了7個(gè)數(shù)據(jù)。
-- Dirty?reads non-repeatable?reads phantom reads
Serializable? 不會(huì) 不會(huì) 不會(huì)
REPEATABLE?READ 不會(huì) 不會(huì) 會(huì)
READ?COMMITTED 不會(huì) 會(huì) 會(huì)
Read?Uncommitted 會(huì) 會(huì) 會(huì)
DEFALT(使用底層數(shù)據(jù)庫的默認(rèn)隔離級(jí)別) ? ?

spring事務(wù)管理其他配置

readOnly

標(biāo)記事務(wù)只讀。只讀事務(wù)會(huì)被優(yōu)化;但是“只讀”事務(wù)中其實(shí)可以寫數(shù)據(jù)。

timeout

事務(wù)超時(shí)時(shí)間

rollbackFor/rollbackForClassName

標(biāo)記哪些異常會(huì)引發(fā)回滾。默認(rèn)情況下,所有RuntimeException都會(huì)引發(fā)回滾;所有其它異常(checked-exception)都不引發(fā)回滾。如果需要針對(duì)某種checked-exception進(jìn)行回滾,則需要為事務(wù)配置rollbackFor或者rollbackForClassName

noRollbackFor/noRollbackForClassName

標(biāo)記哪些異常不會(huì)引發(fā)回滾。默認(rèn)情況下,所有RuntimeException都會(huì)引發(fā)回滾;所有其它異常(checked-exception)都不引發(fā)回滾。如果需要使得某種RuntimeException不進(jìn)行回滾,則需要為事務(wù)配置noRollbackFor/noRollbackForClassName。

spring事務(wù)機(jī)制基本原理

aop

無論配置方式,還是注解方式,spring都是基于spring aop來進(jìn)行事務(wù)管理。
即,在事務(wù)切入點(diǎn)處,生成一個(gè)動(dòng)態(tài)代理。在代理中管理事務(wù)(開啟、傳播、提交或回滾等)??蓞⒁娤旅鎯蓮垐D:
Spring事務(wù)

Spring事務(wù)

兩張圖都是調(diào)用isLocked()方法時(shí)的線程棧。可以看到,第二章圖是通過動(dòng)態(tài)代理來調(diào)用isLocked()方法的,而第一張圖則不是。
動(dòng)態(tài)代理的方式簡化了代碼的開發(fā);但是也引入了一些小問題。后面會(huì)提。

線程上下文

spring會(huì)將事務(wù)相關(guān)的有狀態(tài)數(shù)據(jù)(數(shù)據(jù)庫連接、hibernate的session等)放在線程上下文中(ThreadLocal)。因此,事務(wù)間的傳播關(guān)系、事務(wù)開啟和關(guān)閉的時(shí)機(jī),與線程中的方法調(diào)用棧很相似。
另外,由于事務(wù)與線程相關(guān),因此目前spring的事務(wù)管理無法讓多個(gè)子線程在同一事務(wù)內(nèi)運(yùn)行。

如何測試

首先來看看如何確定運(yùn)行時(shí)是否使用了事務(wù)、事務(wù)是如何傳播的。
首先在log4j中加入以下配置。嚴(yán)格說只要記錄了org.springframework.transaction.support.AbstractPlatformTransactionManager的日志即可。另一個(gè)logger是為了記錄SQL的,也可以將它替換成其它logger。

<Logger????name="org.springframework.transaction.support.AbstractPlatformTransactionManager"????level="DEBUG"?additivity="false">???
        <AppenderRef?ref="Console"?/>????
        <AppenderRef?ref="AuditAsyncAppender"?/>
</Logger>
<Logger?name="org.hibernate.SQL"?level="TRACE"?additivity="false">
        <AppenderRef?ref="Console"?/>?
       <AppenderRef?ref="AuditAsyncAppender"?/>
</Logger>

然后運(yùn)行事務(wù)相關(guān)代碼:

@Testpublic?void?test() {???
        // 這個(gè)方法使用默認(rèn)的傳播方式(REQUIRED)????
        this.lockServiceByDB.tryLock(LockName.EVENT_LOAN,?"23424");????
        // 這個(gè)方法使用REQUIRES_NEW的傳播方式????
        this.lockServiceByDB.isLocked(LockName.EVENT_LOAN,?"23424");
}

可以找到如下日志:

2016-07-06 18:01:10,969 DEBUG AbstractPlatformTransactionManager.getTransaction Creating new transaction with name[cn.youcredit.thread.bizaccount.service.impl.LockServiceByDBTestBySpring.test]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT; '',-cn.youcredit.thread.common.exception.ServiceException
【中略】
2016-07-06 18:01:11,297 DEBUG AbstractPlatformTransactionManager.handleExistingTransaction Participating in existing transaction
2016-07-06 18:01:11,309 DEBUG SqlStatementLogger.logStatement
select count(1) as col_00 from dblocks dblock0 where dblock0.lockName=? and dblock0.uniKey=?
2016-07-06 18:01:11,325 DEBUG SqlStatementLogger.logStatement
insert into db_locks (lockName, lockTimeAsLong, uniKey) values (?, ?, ?)
【中略】
2016-07-06 18:01:11,331 DEBUG AbstractPlatformTransactionManager.handleExistingTransaction Suspending current transaction, creating new transaction with name [cn.youcredit.thread.bizaccount.service.impl.LockServiceByDB.isLocked]
【中略】
2016-07-06 18:01:11,335 DEBUG SqlStatementLogger.logStatement
select count(1) as col_00 from dblocks dblock0 where dblock0.lockName=? and dblock0.uniKey=?
2016-07-06 18:01:11,337 DEBUG AbstractPlatformTransactionManager.processCommit Initiating transaction commit
2016-07-06 18:01:11,337 DEBUG HibernateTransactionManager.doCommit Committing Hibernate transaction on Session [SessionImpl(PersistenceContext[entityKeys=[],collectionKeys=[]];ActionQueue[insertions=org.hibernate.engine.spi.ExecutableList@1a9ea4d9 updates=org.hibernate.engine.spi.ExecutableList@59ca0db6 deletions=org.hibernate.engine.spi.ExecutableList@33293dac orphanRemovals=org.hibernate.engine.spi.ExecutableList@384841e3 collectionCreations=org.hibernate.engine.spi.ExecutableList@571f925f collectionRemovals=org.hibernate.engine.spi.ExecutableList@5eb192b7 collectionUpdates=org.hibernate.engine.spi.ExecutableList@248f1090 collectionQueuedOps=org.hibernate.engine.spi.ExecutableList@5eb20abb unresolvedInsertDependencies=UnresolvedEntityInsertActions[]])]
【中略】
2016-07-06 18:01:11,339 DEBUG AbstractPlatformTransactionManager.cleanupAfterCompletion Resuming suspended transaction after completion of inner transaction
2016-07-06 18:01:11,340 DEBUG AbstractPlatformTransactionManager.proce***ollback Initiating transaction rollback
2016-07-06 18:01:11,340 DEBUG HibernateTransactionManager.doRollback Rolling back Hibernate transaction on Session [SessionImpl(PersistenceContext[entityKeys=[EntityKey[cn.youcredit.thread.common.model.auth.UserInfo#system], EntityKey[cn.youcredit.thread.bizaccount.bean.DBLock#139], EntityKey[cn.youcredit.thread.common.model.auth.UserGroupInfo#500]],collectionKeys=[]];ActionQueue[insertions=org.hibernate.engine.spi.ExecutableList@2f90ad92 updates=org.hibernate.engine.spi.ExecutableList@4710332d deletions=org.hibernate.engine.spi.ExecutableList@7930de44 orphanRemovals=org.hibernate.engine.spi.ExecutableList@52891a77 collectionCreations=org.hibernate.engine.spi.ExecutableList@785fd189 collectionRemovals=org.hibernate.engine.spi.ExecutableList@3e900cf4 collectionUpdates=org.hibernate.engine.spi.ExecutableList@412d379c collectionQueuedOps=org.hibernate.engine.spi.ExecutableList@5b6dc76c unresolvedInsertDependencies=UnresolvedEntityInsertActions[]])]

從日志中可以清楚的看到事務(wù)操作、傳播的過程:

  1. 首先,運(yùn)行LockServiceByDBTestBySpring.test()方法時(shí),創(chuàng)建了一個(gè)新事務(wù)。這是SpringJUnit4Cla***unner的事務(wù)邏輯,即每一個(gè)單元測試都會(huì)啟動(dòng)一個(gè)事務(wù),并且默認(rèn)情況下,該事務(wù)會(huì)回滾。
  2. 然后,在執(zhí)行到this.lockServiceByDB.tryLock()方法時(shí),由于傳播方式是REQUIRED,因此會(huì)加入當(dāng)當(dāng)前已有的事務(wù)中,并執(zhí)行兩條sql。
  3. 接著執(zhí)行this.lockServiceByDB.isLocked()方法。由于這個(gè)方法的事務(wù)傳播方式是REQUIRES_NEW,因此會(huì)掛起當(dāng)前事務(wù),并創(chuàng)建一個(gè)新的事務(wù),在新的事務(wù)中執(zhí)行一條SQL。
  4. 新事務(wù)執(zhí)行并提交之后,恢復(fù)被掛起的上層事務(wù)。并繼續(xù)執(zhí)行。由于后面沒有其它邏輯、代碼,因此開始回滾外層事務(wù)。

常見問題

事務(wù)標(biāo)記在protcted或private方法上,導(dǎo)致事務(wù)失效

spring的事務(wù)注解只對(duì)public方法生效,對(duì)protcted、friendly(默認(rèn))、private方法無效。如果在后面這些方法上標(biāo)記事務(wù)注解,其效果等于沒有標(biāo)記。

注解繼承不當(dāng),導(dǎo)致事務(wù)失效

Java注解的基本繼承關(guān)系如下。spring的Transactional注解上有Inherited的元注解。

- 未寫@Inherited: 寫了@Inherited的
子類的類上能否繼承到父類的類上的注解?
子類方法,實(shí)現(xiàn)了父類上的抽象方法,這個(gè)方法能否繼承到注解?
子類方法,繼承了父類上的方法,這個(gè)方法能否繼承到注解?
子類方法,覆蓋了父類上的方法,這個(gè)方法能否繼承到注解?

例如下面的代碼中,雖然Son在類級(jí)別上聲明了事務(wù),但是它的tryLocked()方法并不會(huì)啟動(dòng)新的事務(wù)。因?yàn)樗诟割愔袥]有聲明事務(wù)。

public?class?Father{
    public?void?tryLocked(){}
}

@Transactional(REQUIRES_NEW)
public?class?Son?extends?Father{
}

使用this方式調(diào)用,導(dǎo)致事務(wù)失效

對(duì)于spring aop的動(dòng)態(tài)代理來說,被代理實(shí)例和方法是一個(gè)“黑盒”。只有在“黑盒”之外才能進(jìn)行事務(wù)管理。而this調(diào)用是黑盒內(nèi)部的調(diào)用邏輯,代理無法感知。
因此,像下文這樣的代碼中,isLocked()方法并不會(huì)啟動(dòng)新的事務(wù)。

@Transactional(REQUIRES_NEW)
    public?void?isLocked(){
        ……
    }?
    @Transactional()
    public?void?tryLock(){?
        this.isLocked();?
        ……
    }

事務(wù)與查詢

雖然查詢操作并不更新數(shù)據(jù),但是查詢也需要事務(wù)。尤其對(duì)hibernate來說。

hibernate操作數(shù)據(jù)庫時(shí),需要獲取到一個(gè)hibernate的session。而這個(gè)session也由HibernateTransactionManager來管理。管理的方式與其它有狀態(tài)數(shù)據(jù)一樣,都是放在ThreadLocal中。如果線程上沒有事務(wù)管理器,那么就拿不到session。hibernate做查詢時(shí)就會(huì)報(bào)錯(cuò):

org.hibernate.HibernateException: Could not obtain transaction-synchronized Session for current thread
at org.springframework.orm.hibernate4.SpringSessionContext.currentSession(SpringSessionContext.java:134) ~[spring-orm-4.2.2.RELEASE.jar:4.2.2.RELEASE]

有時(shí)候明明沒有寫事務(wù)注解,也能執(zhí)行查詢,那么多半是在某個(gè)地方默認(rèn)、或者“偷偷”開了事務(wù)。比如繼承SpringTestCase的單元測試會(huì)默認(rèn)開啟事務(wù)?;蛘遷eb環(huán)境下,線程池中的線程上遺留了以前綁定的事務(wù)管理器。

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請(qǐng)聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI