Spring事務

spring事務基本配置

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

spring事務傳播機制

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

簡單說一下new和nested的區別。
使用new的時候,外層事務的提交或回滾,與new的事務沒有關係。而使用nested時,內層事務最終是提交仍是回滾,須要依賴於外層事務。參見下表。web

事務傳播配置 外層事務(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 回滾 回滾 什麼都不變

spring事務隔離機制

參見:Isolation Level(事務隔離等級): spring

  1. Serializable:最嚴格的級別,事務串行執行,資源消耗最大;
  2. REPEATABLE READ:保證了一個事務不會修改已經由另外一個事務讀取但未提交(回滾)的數據。避免了「髒讀取」和「不可重複讀取」的狀況,可是帶來了更多的性能損失。
  3. READ COMMITTED:大多數主流數據庫的默認事務等級,保證了一個事務不會讀到另外一個並行事務已修改但未提交的數據,避免了「髒讀取」。該級別適用於大多數系統。
  4. Read Uncommitted:保證了讀取過程當中不會讀取到非法數據。隔離級別在於處理多事務的併發問題。

咱們知道並行能夠提升數據庫的吞吐量和效率,可是並非全部的併發事務均可以併發運行,這須要查看數據庫教材的可串行化條件判斷了。sql

這裏就不闡述了。
咱們首先說併發中可能發生的3中不討人喜歡的事情 數據庫

  1. Dirty reads--讀髒數據。也就是說,好比事務A的未提交(還依然緩存)的數據被事務B讀走,若是事務A失敗回滾,會致使事務B所讀取的的數據是錯誤的。
  2. non-repeatable reads--數據不可重複讀。好比事務A中兩處讀取數據-total-的值。在第一讀的時候,total是100,而後事務B就把total的數據改爲200,事務A再讀一次,結果就發現,total居然就變成200了,形成事務A數據混亂。
  3. phantom reads--幻象讀數據,這個和non-repeatable reads類似,也是同一個事務中屢次讀不一致的問題。可是non-repeatable reads的不一致是由於他所要取的數據集被改變了(好比total的數據),可是phantom reads所要讀的數據的不一致卻不是他所要讀的數據集改變,而是他的條件數據集改變。好比Select account.id where account.name="ppgogo*",第一次讀去了6個符合條件的id,第二次讀取的時候,因爲事務b把一個賬號的名字由"dd"改爲"ppgogo1",結果取出來了7個數據。
-- Dirty reads non-repeatable reads phantom reads
Serializable  不會 不會 不會
REPEATABLE READ 不會 不會
READ COMMITTED 不會
Read Uncommitted
DEFALT(使用底層數據庫的默認隔離級別)

spring事務管理其餘配置

readOnly

標記事務只讀。只讀事務會被優化;可是「只讀」事務中其實能夠寫數據。緩存

timeout

事務超時時間session

rollbackFor/rollbackForClassName

標記哪些異常會引起回滾。默認狀況下,全部RuntimeException都會引起回滾;全部其它異常(checked-exception)都不引起回滾。若是須要針對某種checked-exception進行回滾,則須要爲事務配置rollbackFor或者rollbackForClassName併發

noRollbackFor/noRollbackForClassName

標記哪些異常不會引起回滾。默認狀況下,全部RuntimeException都會引起回滾;全部其它異常(checked-exception)都不引起回滾。若是須要使得某種RuntimeException不進行回滾,則須要爲事務配置noRollbackFor/noRollbackForClassName。ide

spring事務機制基本原理

aop

不管配置方式,仍是註解方式,spring都是基於spring aop來進行事務管理。
即,在事務切入點處,生成一個動態代理。在代理中管理事務(開啓、傳播、提交或回滾等)。可參見下面兩張圖:
不經過代理來調用

經過代理來調用

兩張圖都是調用isLocked()方法時的線程棧。能夠看到,第二章圖是經過動態代理來調用isLocked()方法的,而第一張圖則不是。
動態代理的方式簡化了代碼的開發;可是也引入了一些小問題。後面會提。

線程上下文

spring會將事務相關的有狀態數據(數據庫鏈接、hibernate的session等)放在線程上下文中(ThreadLocal)。所以,事務間的傳播關係、事務開啓和關閉的時機,與線程中的方法調用棧很類似。
另外,因爲事務與線程相關,所以目前spring的事務管理沒法讓多個子線程在同一事務內運行。

如何測試

首先來看看如何肯定運行時是否使用了事務、事務是如何傳播的。
首先在log4j中加入如下配置。嚴格說只要記錄了org.springframework.transaction.support.AbstractPlatformTransactionManager的日誌便可。另外一個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>

而後運行事務相關代碼:

@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[]])]

從日誌中能夠清楚的看到事務操做、傳播的過程:

  1. 首先,運行LockServiceByDBTestBySpring.test()方法時,建立了一個新事務。這是SpringJUnit4Cla***unner的事務邏輯,即每個單元測試都會啓動一個事務,而且默認狀況下,該事務會回滾。
  2. 而後,在執行到this.lockServiceByDB.tryLock()方法時,因爲傳播方式是REQUIRED,所以會加入噹噹前已有的事務中,並執行兩條sql。
  3. 接着執行this.lockServiceByDB.isLocked()方法。因爲這個方法的事務傳播方式是REQUIRES_NEW,所以會掛起當前事務,並建立一個新的事務,在新的事務中執行一條SQL。
  4. 新事務執行並提交以後,恢復被掛起的上層事務。並繼續執行。因爲後面沒有其它邏輯、代碼,所以開始回滾外層事務。

常見問題

事務標記在protcted或private方法上,致使事務失效

spring的事務註解只對public方法生效,對protcted、friendly(默認)、private方法無效。若是在後面這些方法上標記事務註解,其效果等於沒有標記。

註解繼承不當,致使事務失效

Java註解的基本繼承關係以下。spring的Transactional註解上有Inherited的元註解。

- 未寫@Inherited: 寫了@Inherited的
子類的類上可否繼承到父類的類上的註解?
子類方法,實現了父類上的抽象方法,這個方法可否繼承到註解?
子類方法,繼承了父類上的方法,這個方法可否繼承到註解?
子類方法,覆蓋了父類上的方法,這個方法可否繼承到註解?

例以下面的代碼中,雖然Son在類級別上聲明瞭事務,可是它的tryLocked()方法並不會啓動新的事務。由於它在父類中沒有聲明事務。

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

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

使用this方式調用,致使事務失效

對於spring aop的動態代理來講,被代理實例和方法是一個「黑盒」。只有在「黑盒」以外才能進行事務管理。而this調用是黑盒內部的調用邏輯,代理沒法感知。
所以,像下文這樣的代碼中,isLocked()方法並不會啓動新的事務。

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

事務與查詢

雖然查詢操做並不更新數據,可是查詢也須要事務。尤爲對hibernate來講。

hibernate操做數據庫時,須要獲取到一個hibernate的session。而這個session也由HibernateTransactionManager來管理。管理的方式與其它有狀態數據同樣,都是放在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]

有時候明明沒有寫事務註解,也能執行查詢,那麼多半是在某個地方默認、或者「偷偷」開了事務。好比繼承SpringTestCase的單元測試會默認開啓事務。或者web環境下,線程池中的線程上遺留了之前綁定的事務管理器。

相關文章
相關標籤/搜索