參見:http://www.cnblogs.com/leiOOlei/p/3725911.html
十余年建站經驗, 做網站、成都網站設計客戶的見證與正確選擇。創(chuàng)新互聯(lián)提供完善的營銷型網頁建站明細報價表。后期開發(fā)更加便捷高效,我們致力于追求更美、更快、更規(guī)范。
參見:http://www.cnblogs.com/aurawing/articles/1887030.html
簡單說一下new和nested的區(qū)別。
使用new的時候,外層事務的提交或回滾,與new的事務沒有關系。而使用nested時,內層事務最終是提交還是回滾,需要依賴于外層事務。參見下表。
事務傳播配置 | 外層事務(set a=1) | 內層事務(set b=2) | 最終結果 |
---|---|---|---|
new | 提交 | 提交 | a=1 && b=2 |
new | 提交 | 回滾 | a=1 |
new | 回滾 | 提交 | b=2 |
new | 回滾 | 回滾 | 什么都不變 |
nested | 提交 | 提交 | a=1 && b=2 |
nested | 提交 | 回滾 | a=1(這種情況需要增加一個配置:<property name="globalRollbackOnParticipationFailure" value="false" /> ) |
nested | 回滾 | 提交 | 什么都不變 |
nested | 回滾 | 回滾 | 什么都不變 |
參見:Isolation Level(事務隔離等級):
我們知道并行可以提高數(shù)據(jù)庫的吞吐量和效率,但是并不是所有的并發(fā)事務都可以并發(fā)運行,這需要查看數(shù)據(jù)庫教材的可串行化條件判斷了。
這里就不闡述了。
我們首先說并發(fā)中可能發(fā)生的3中不討人喜歡的事情
-- | Dirty?reads | non-repeatable?reads | phantom reads |
---|---|---|---|
Serializable? | 不會 | 不會 | 不會 |
REPEATABLE?READ | 不會 | 不會 | 會 |
READ?COMMITTED | 不會 | 會 | 會 |
Read?Uncommitted | 會 | 會 | 會 |
DEFALT(使用底層數(shù)據(jù)庫的默認隔離級別) | ? | ? | ? |
標記事務只讀。只讀事務會被優(yōu)化;但是“只讀”事務中其實可以寫數(shù)據(jù)。
事務超時時間
標記哪些異常會引發(fā)回滾。默認情況下,所有RuntimeException都會引發(fā)回滾;所有其它異常(checked-exception)都不引發(fā)回滾。如果需要針對某種checked-exception進行回滾,則需要為事務配置rollbackFor或者rollbackForClassName
標記哪些異常不會引發(fā)回滾。默認情況下,所有RuntimeException都會引發(fā)回滾;所有其它異常(checked-exception)都不引發(fā)回滾。如果需要使得某種RuntimeException不進行回滾,則需要為事務配置noRollbackFor/noRollbackForClassName。
無論配置方式,還是注解方式,spring都是基于spring aop來進行事務管理。
即,在事務切入點處,生成一個動態(tài)代理。在代理中管理事務(開啟、傳播、提交或回滾等)。可參見下面兩張圖:
兩張圖都是調用isLocked()方法時的線程棧。可以看到,第二章圖是通過動態(tài)代理來調用isLocked()方法的,而第一張圖則不是。
動態(tài)代理的方式簡化了代碼的開發(fā);但是也引入了一些小問題。后面會提。
spring會將事務相關的有狀態(tài)數(shù)據(jù)(數(shù)據(jù)庫連接、hibernate的session等)放在線程上下文中(ThreadLocal)。因此,事務間的傳播關系、事務開啟和關閉的時機,與線程中的方法調用棧很相似。
另外,由于事務與線程相關,因此目前spring的事務管理無法讓多個子線程在同一事務內運行。
首先來看看如何確定運行時是否使用了事務、事務是如何傳播的。
首先在log4j中加入以下配置。嚴格說只要記錄了org.springframework.transaction.support.AbstractPlatformTransactionManager的日志即可。另一個logger是為了記錄SQL的,也可以將它替換成其它logger。
???
????
?
然后運行事務相關代碼:
@Testpublic?void?test() {???
// 這個方法使用默認的傳播方式(REQUIRED)????
this.lockServiceByDB.tryLock(LockName.EVENT_LOAN,?"23424");????
// 這個方法使用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[]])]
從日志中可以清楚的看到事務操作、傳播的過程:
spring的事務注解只對public方法生效,對protcted、friendly(默認)、private方法無效。如果在后面這些方法上標記事務注解,其效果等于沒有標記。
Java注解的基本繼承關系如下。spring的Transactional注解上有Inherited的元注解。
- | 未寫@Inherited: | 寫了@Inherited的 |
---|---|---|
子類的類上能否繼承到父類的類上的注解? | 否 | 能 |
子類方法,實現(xiàn)了父類上的抽象方法,這個方法能否繼承到注解? | 否 | 否 |
子類方法,繼承了父類上的方法,這個方法能否繼承到注解? | 能 | 能 |
子類方法,覆蓋了父類上的方法,這個方法能否繼承到注解? | 否 | 否 |
例如下面的代碼中,雖然Son在類級別上聲明了事務,但是它的tryLocked()方法并不會啟動新的事務。因為它在父類中沒有聲明事務。
public?class?Father{
public?void?tryLocked(){}
}
@Transactional(REQUIRES_NEW)
public?class?Son?extends?Father{
}
對于spring aop的動態(tài)代理來說,被代理實例和方法是一個“黑盒”。只有在“黑盒”之外才能進行事務管理。而this調用是黑盒內部的調用邏輯,代理無法感知。
因此,像下文這樣的代碼中,isLocked()方法并不會啟動新的事務。
@Transactional(REQUIRES_NEW)
public?void?isLocked(){
……
}?
@Transactional()
public?void?tryLock(){?
this.isLocked();?
……
}
雖然查詢操作并不更新數(shù)據(jù),但是查詢也需要事務。尤其對hibernate來說。
hibernate操作數(shù)據(jù)庫時,需要獲取到一個hibernate的session。而這個session也由HibernateTransactionManager來管理。管理的方式與其它有狀態(tài)數(shù)據(jù)一樣,都是放在ThreadLocal中。如果線程上沒有事務管理器,那么就拿不到session。hibernate做查詢時就會報錯:
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]
有時候明明沒有寫事務注解,也能執(zhí)行查詢,那么多半是在某個地方默認、或者“偷偷”開了事務。比如繼承SpringTestCase的單元測試會默認開啟事務?;蛘遷eb環(huán)境下,線程池中的線程上遺留了以前綁定的事務管理器。