1. 背景
最近讀了Spring聲明式事務相關源碼,現在將相關原理及本人注釋過的實現源碼整理到博客上並對一些工作中的案例與事務源碼中的參數進行總結。
2. 基本概念
2.1 基本名詞解釋
名詞 | 概念 |
---|---|
PlatformTransactionManager | 事務管理器,管理事務的各生命周期方法,下文簡稱TxMgr |
TransactionAttribute | 事務屬性, 包含隔離級別,傳播行為,是否只讀等信息,下文簡稱TxAttr |
TransactionStatus | 事務狀態,下文簡稱TxStatus |
TransactionInfo | 事務信息,內含TxMgr, TxAttr, TxStatus等信息,下文簡稱TxInfo |
TransactionSynchronization | 事務同步回調,內含多個鈎子方法,下文簡稱TxSync / transaction synchronization |
TransactionSynchronizationManager | 事務同步管理器,維護當前線程事務資源,信息以及TxSync集合 |
2.2 七種事務傳播行為
Spring中的Propagation枚舉和TransactionDefinition接口定義了7種事務傳播行為:
- REQUIRED
如果當前無事務則開啟一個事務,否則加入當前事務。 - SUPPORTS
如果當前有事務則加入當前事務。 - MANDATORY
如果當前無事務則拋出異常,否則加入當前事務。 - REQUIRES_NEW
如果當前無事務則開啟一個事務,否則掛起當前事務並開啟新事務。 - NOT_SUPPORTED
如果當前有事務,則掛起當前事務以無事務狀態執行方法。 - NEVER
如果當前有事務,則拋出異常。 - NESTED
創建一個嵌套事務,如果當前無事務則創建一個事務。
2.3 套路講解
這里介紹以下Spring聲明式事務的套路。如果能知曉大致套路會對接下來看源碼有很大的幫助。文筆不是太好,下面的描述如有不清,還請指出。
2.3.1 事務攔截器
我們給一個bean的方法加上@Transactional
注解后,Spring容器給我們的是一個代理的bean。當我們對事務方法調用時,會進入Spring的ReflectiveMethodInvocation#proceed方法。這是AOP的主要實現,在進入業務方法前會調用各種方法攔截器,我們需要關注的攔截器是org.springframework.transaction.interceptor.TransactionInterceptor。
TransactionInterceptor的職責類似於一個“環繞切面”,在業務方法調用前根據情況開啟事務,在業務方法調用完回到攔截器后進行善后清理。
事務切面在源碼中具體的實現方法是TransactionAspectSupport#invokeWithinTransaction,相信平時大家debug的時候在調用棧中經常能看到此方法。事務切面關注的是TransactionInfo(TxInfo),TxInfo是一個“非常大局觀”的東西(里面啥都有:TxMgr, TxAttr, TxStatus還有前一次進入事務切面的TransactionInfo)。
因此事務切面會調用createTransactionIfNecessary方法來創建事務並拿到一個TxInfo(無論是否真的物理創建了一個事務)。如果事務塊內的代碼發生了異常,則會根據TxInfo里面的TxAttr配置的rollback規則看看這個異常是不是需要回滾,不需要回滾就嘗試提交,否則就嘗試回滾。如果未發生異常,則嘗試提交。
2.3.2 提交與回滾
事務切面對於嘗試提交會判斷是否到了最外層事務(某個事務邊界)。舉個例子:有四個事務方法依次調用,傳播行為分別是 方法1:REQUIRED, 方法2:REQUIRED, 方法3: REQUIRES_NEW, 方法4: REQUIRED。很顯然這其中包含了兩個獨立的物理事務,當退棧到方法4的事務切面時,會發現沒有到事務最外層,所以不會有真正的物理提交。而在退棧到了方法3對應的事務切面時會發現是外層事務,此時會發生物理提交。同理,退棧到方法1的事務切面時也會觸發物理提交。
那么問題來了,Spring是怎么判斷這所謂“最外層事務”的呢。
答案是TxStatus中有個屬性叫newTransaction用於標記是否是新建事務(根據事務傳播行為得出,比如加入已有事務則會是false),以及一個名為transaction的Object用於表示物理事務對象(由具體TxMgr子類負責給出)。Spring會根據每一層事務切面創建的TxStatus內部是否持有transaction對象以及newTransaction標志位判斷是否屬於外層事務。
類似的,Spring對於回滾事務也是會在最外層事務方法對應的切面中進行物理回滾。而在非最外層事務的時候會由具體txMgr子類給對應的事務打個的標記用於標識這個事務該回滾,這樣的話在所有同一物理事務方法退棧過程中在事務切面中都能讀取到事務被打了應該回滾的標記。可以說這是同一物理事務方法之間進行通信的機制。
2.3.3 ThreadLocal的使用
Spring事務代碼中用ThreadLocal來進行資源與事務的生命周期的同步管理。
在事務切面層面,TransactionAspectSupport里面有個transactionInfoHolder的ThreadLocal對象,用於把TxInfo綁定到線程。那么這樣在我們的業務代碼或者其他切面中,我們可以拿到TxInfo,也能拿到TxStatus。拿到TxStatus我們就可以調用setRollbackOnly來打標以手動控制事務必須回滾。
TransactionSynchronizationManager是Spring事務代碼中對ThreadLocal使用最多的類,目前它內部含有6個ThreadLocal,分別是:
- resources
類型為Map<Object, Object>
用於保存事務相關資源,比如我們常用的DataSourceTransactionManager會在開啟物理事務的時候把<DataSource, ConnectionHolder>
綁定到線程。
這樣在事務作用的業務代碼中可以通過Spring的DataSourceUtils拿到綁定到線程的ConnectionHolder中的Connection。事實上對於MyBatis來說與Spring集成時就是這樣拿的。 - synchronizations
類型為Set<TransactionSynchronization>
用於保存transaction synchronization,這個可以理解為是回調鈎子對象,內部含有beforeCommit, afterCommit, beforeCompletion等鈎子方法。
我們自己如果需要的話也可以在業務方法或者切面中注冊一些transaction synchronization對象用於追蹤事務生命周期做一些自定義的事情。 - currentTransactionName
當前事務名 - currentTransactionReadOnly
當前事務是否只讀 - currentTransactionIsolationLevel
當前事務隔離級別 - actualTransactionActive
是否存在物理事務,比如傳播行為為NOT_SUPPORTED時就會是false。
2.4 幾幅草圖
下面是我做的幾張圖,比較丑陋。舉了三個不同的事務傳播情況,列一下TxInfo的信息(TxInfo中主要列了TxStatus的一些關鍵字段以及oldTransactionInfo字段)
最常見的REQUIRED調用REQUIRED
REQUIRED調用REQUIRES_NEW
REQUIRED調用NOT_SUPPORTED
3.源碼實現
直接進入源碼實現部分,最好需要對Spring AOP,攔截器,代理等有個基本認知閱讀起來會比較輕松。
3.1 事務的入口--TransactionInterceptor
TransactionInterceptor是Spring實現聲明式事務的攔截器,它實現了AOP聯盟的MethodInterceptor接口,它的父類TransactionAspectSupport封裝了一些用於實現事務切面對事務進行管理的基本代碼。下面來看一下TransactionInterceptor的繼承關系。
3.1.1 TransactionInterceptor#invoke
TransactionInterceptor對於MethodInterceptor#invoke的實現很簡單,就是調用父類的的invokeWithinTransaction,並傳遞給此方法一個回調用於繼續后續的攔截調用。
@Override
public Object invoke(final MethodInvocation invocation) throws Throwable {
// 因為這里的invocation.getThis可能是一個代理類,需要獲取目標原生class。
Class<?> targetClass = (invocation.getThis() != null ? AopUtils.getTargetClass(invocation.getThis()) : null);
// 調用父類TransactionAspectSupport的invokeWithinTransaction方法,第三個參數是一個簡易回調實現,用於繼續方法調用鏈。
return invokeWithinTransaction(invocation.getMethod(), targetClass, new InvocationCallback() {
@Override
public Object proceedWithInvocation() throws Throwable {
return invocation.proceed();
}
});
}
3.1.2 TransactionAspectSupport#invokeWithinTransaction
這里就是上面TransactionInterceptor調用的invokeWithinTransaction實現,可以將之看作是一個大的環繞切面,將事務的創建與提交/回滾包在事務方法的外圍。
protected Object invokeWithinTransaction(Method method, Class<?> targetClass, final InvocationCallback invocation) throws Throwable {
// 獲取TransactionAttribute、PlatformTransactionManager、以及連接點方法信息。
final TransactionAttribute txAttr = getTransactionAttributeSource().getTransactionAttribute(method, targetClass);
final PlatformTransactionManager tm = determineTransactionManager(txAttr);
final String joinpointIdentification = methodIdentification(method, targetClass, txAttr);
if (txAttr == null || !(tm instanceof CallbackPreferringPlatformTransactionManager)) {
// Standard transaction demarcation with getTransaction and commit/rollback calls.
// 根據上面抓取出來的txAttribute, tm, 連接點方法等信息判斷是否需要開啟事務。
TransactionInfo txInfo = createTransactionIfNecessary(tm, txAttr, joinpointIdentification);
Object retVal = null;
try {
// 執行回調,如果沒有后續攔截器的話,就進入事務方法了。
retVal = invocation.proceedWithInvocation();
}
catch (Throwable ex) {
// 事務發生異常。
completeTransactionAfterThrowing(txInfo, ex);
throw ex;
}
finally {
// 把上一層事務的TxInfo重新綁到ThreadLocal中。
cleanupTransactionInfo(txInfo);
}
// 事務未發生異常。
commitTransactionAfterReturning(txInfo);
return retVal;
}
else {
try {
Object result = ((CallbackPreferringPlatformTransactionManager) tm).execute(txAttr,
new TransactionCallback<Object>() {
@Override
public Object doInTransaction(TransactionStatus status) {
TransactionInfo txInfo = prepareTransactionInfo(tm, txAttr, joinpointIdentification, status);
try {
return invocation.proceedWithInvocation();
}
catch (Throwable ex) {
if (txAttr.rollbackOn(ex)) {
if (ex instanceof RuntimeException) {
throw (RuntimeException) ex;
}
else {
throw new ThrowableHolderException(ex);
}
}
else {
return new ThrowableHolder(ex);
}
}
finally {
cleanupTransactionInfo(txInfo);
}
}
});
if (result instanceof ThrowableHolder) {
throw ((ThrowableHolder) result).getThrowable();
}
else {
return result;
}
}
catch (ThrowableHolderException ex) {
throw ex.getCause();
}
}
}
3.1.3 TransactionAspectSupport#createTransactionIfNecessary
protected TransactionInfo createTransactionIfNecessary( PlatformTransactionManager tm, TransactionAttribute txAttr, final String joinpointIdentification) {
// 如果事務屬性中name為null,則創建一個簡易委托類,name為連接點方法標識。
if (txAttr != null && txAttr.getName() == null) {
txAttr = new DelegatingTransactionAttribute(txAttr) {
@Override
public String getName() {
return joinpointIdentification;
}
};
}
TransactionStatus status = null;
if (txAttr != null) {
if (tm != null) {
// 根據事務屬性判斷是否需要開啟事務,並返回狀態。
status = tm.getTransaction(txAttr);
}
else {
if (logger.isDebugEnabled()) {
logger.debug("Skipping transactional joinpoint [" + joinpointIdentification +
"] because no transaction manager has been configured");
}
}
}
return prepareTransactionInfo(tm, txAttr, joinpointIdentification, status);
}
protected TransactionInfo prepareTransactionInfo(PlatformTransactionManager tm,
TransactionAttribute txAttr, String joinpointIdentification, TransactionStatus status) {
TransactionInfo txInfo = new TransactionInfo(tm, txAttr, joinpointIdentification);
// 事務方法。
if (txAttr != null) {
if (logger.isTraceEnabled()) {
logger.trace("Getting transaction for [" + txInfo.getJoinpointIdentification() + "]");
}
// The transaction manager will flag an error if an incompatible tx already exists.
txInfo.newTransactionStatus(status);
}
else {
// 非事務方法。
if (logger.isTraceEnabled())
logger.trace("Don't need to create transaction for [" + joinpointIdentification +
"]: This method isn't transactional.");
}
// 無論是否創建了新事務,這里都會把當前的txInfo對象通過threadLocal變量綁定到當前線程。
txInfo.bindToThread();
return txInfo;
}
3.2 事務的管理--AbstractPlatformTransactionManager
AbstractPlatformTransactionManager是各種事務管理器的抽象基類,也可以說是骨架。它封裝了很多事務管理的流程代碼,子類需要實現一些模板方法。下面列出一些主要的模板方法。
- doGetTransaction
用於從TxMgr中拿一個事務對象,事務對象具體什么類型AbstractPlatformTransactionManager並不care。如果當前已經有事務的話,返回的對象應該是要包含當前事務信息的。 - isExistingTransaction
用於判斷一個事務對象是否對應於一個已經存在的事務。Spring會根據這個方法的返回值來分類討論事務該如何傳播。 - doBegin
物理開啟事務。 - doSuspend
將當前事務資源掛起。對於我們常用的DataSourceTransactionManager,它的資源就是ConnectionHolder。會將ConnectionHolder與當前線程脫鈎並取出來。 - doResume
恢復當前事務資源。對於我們常用的DataSourceTransactionManager,它會將ConnectionHolder重新綁定到線程上。 - doCommit
物理提交事務。 - doRollback
物理回滾事務。 - doSetRollbackOnly
給事務標記為回滾。對於我們常用的DataSourceTransactionManager,它的實現是拿出事務對象中的ConnectionHolder打上回滾標記。這個標記是一種“全局的標記”,因為隸屬於同一個物理事務都能讀到同一個ConnectionHolder。
我們首先從上面createTransactionIfNecessary方法中調用到的getTransaction方法開始看起。
public final TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException {
// 根據具體的tm實現獲取一個transaction對象。
Object transaction = doGetTransaction();
boolean debugEnabled = logger.isDebugEnabled();
if (definition == null) {
definition = new DefaultTransactionDefinition();
}
// 已經存在事務的情況。
if (isExistingTransaction(transaction)) {
return handleExistingTransaction(definition, transaction, debugEnabled);
}
// timeout不能小於-1。
if (definition.getTimeout() < TransactionDefinition.TIMEOUT_DEFAULT) {
throw new InvalidTimeoutException("Invalid transaction timeout", definition.getTimeout());
}
// 如果傳播行為是MANDATORY,則應該拋出異常(因為此時不存在事務)
if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_MANDATORY) {
throw new IllegalTransactionStateException(
"No existing transaction found for transaction marked with propagation 'mandatory'");
}
// 傳播行為是REQUIRED, REQUIRES_NEW, NESTED。
else if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRED ||
definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRES_NEW ||
definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NESTED) {
SuspendedResourcesHolder suspendedResources = suspend(null);
if (debugEnabled) {
logger.debug("Creating new transaction with name [" + definition.getName() + "]: " + definition);
}
try {
boolean newSynchronization = (getTransactionSynchronization() != SYNCHRONIZATION_NEVER);
// 注意這里的newTransaction標識位是true。
DefaultTransactionStatus status = newTransactionStatus(
definition, transaction, true, newSynchronization, debugEnabled, suspendedResources);
// 調用供子類實現的模板方法doBegin來開啟事務。
doBegin(transaction, definition);
// 調用TransactionSynchronizationManager來保存一些事務上下文信息。
prepareSynchronization(status, definition);
return status;
}
catch (RuntimeException ex) {
resume(null, suspendedResources);
throw ex;
}
catch (Error err) {
resume(null, suspendedResources);
throw err;
}
}
/*
* 這里的else分支說明傳播行為是SUPPORTS或NOT_SUPPORTED或NEVER,這幾種情況對於當前無事務的邏輯都是直接繼續運行。
*/
else {
// 如果有指定事務隔離級別,則可以打warn日志報出指定隔離級別開啟但沒有事務的警告。
if (definition.getIsolationLevel() != TransactionDefinition.ISOLATION_DEFAULT && logger.isWarnEnabled()) {
logger.warn("Custom isolation level specified but no actual transaction initiated; " +
"isolation level will effectively be ignored: " + definition);
}
boolean newSynchronization = (getTransactionSynchronization() == SYNCHRONIZATION_ALWAYS);
// 注意這里的transaction傳的是null,這在嘗試commit的時候會判斷出其實沒有實際需要提交的事務。
return prepareTransactionStatus(definition, null, true, newSynchronization, debugEnabled, null);
}
}
/**
* prepareSynchronization方法根據status是否需要維護新的事務相關資源,信息與回調。
*/
protected void prepareSynchronization(DefaultTransactionStatus status, TransactionDefinition definition) {
if (status.isNewSynchronization()) {
TransactionSynchronizationManager.setActualTransactionActive(status.hasTransaction());
TransactionSynchronizationManager.setCurrentTransactionIsolationLevel(
definition.getIsolationLevel() != TransactionDefinition.ISOLATION_DEFAULT ?
definition.getIsolationLevel() : null);
TransactionSynchronizationManager.setCurrentTransactionReadOnly(definition.isReadOnly());
TransactionSynchronizationManager.setCurrentTransactionName(definition.getName());
TransactionSynchronizationManager.initSynchronization();
}
}
下面我們來看一下對於當前已經有事務的情況下,Spring是如何處理的:
private TransactionStatus handleExistingTransaction( TransactionDefinition definition, Object transaction, boolean debugEnabled) throws TransactionException {
// 傳播行為為NEVER,拋出異常。
if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NEVER) {
throw new IllegalTransactionStateException(
"Existing transaction found for transaction marked with propagation 'never'");
}
// 傳播行為為NOT_SUPPORTED,則掛起當前事務。
if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NOT_SUPPORTED) {
if (debugEnabled) {
logger.debug("Suspending current transaction");
}
Object suspendedResources = suspend(transaction);
boolean newSynchronization = (getTransactionSynchronization() == SYNCHRONIZATION_ALWAYS);
return prepareTransactionStatus(
definition, null, false, newSynchronization, debugEnabled, suspendedResources);
}
// 傳播行為為REQUIRES_NEW,則掛起當前事務並開啟新事務運行。
if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRES_NEW) {
if (debugEnabled) {
logger.debug("Suspending current transaction, creating new transaction with name [" +
definition.getName() + "]");
}
SuspendedResourcesHolder suspendedResources = suspend(transaction);
try {
boolean newSynchronization = (getTransactionSynchronization() != SYNCHRONIZATION_NEVER);
DefaultTransactionStatus status = newTransactionStatus(
definition, transaction, true, newSynchronization, debugEnabled, suspendedResources);
// 開啟事務,由具體子類TxMgr實現。
doBegin(transaction, definition);
prepareSynchronization(status, definition);
return status;
}
catch (RuntimeException beginEx) {
resumeAfterBeginException(transaction, suspendedResources, beginEx);
throw beginEx;
}
catch (Error beginErr) {
resumeAfterBeginException(transaction, suspendedResources, beginErr);
throw beginErr;
}
}
// 傳播行為為NESTED。
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()) {
DefaultTransactionStatus status =
prepareTransactionStatus(definition, transaction, false, false, debugEnabled, null);
// 這里最終會委托SavePointManager去創建還原點。
status.createAndHoldSavepoint();
return status;
}
else {
// 通常tm如果是JTA的話會走到這里來,這種情況就通過嵌套的begin和commit/rollback實現。
boolean newSynchronization = (getTransactionSynchronization() != SYNCHRONIZATION_NEVER);
DefaultTransactionStatus status = newTransactionStatus(
definition, transaction, true, newSynchronization, debugEnabled, null);
doBegin(transaction, definition);
prepareSynchronization(status, definition);
return status;
}
}
// 此時說明傳播行為為SUPPORTS或REQUIRED或MANDATORY,則直接加入當前事務即可。
if (debugEnabled) {
logger.debug("Participating in existing transaction");
}
/*
* validateExistingTransaction是用來對SUPPORTS和REQUIRED的傳播行為進行事務定義校驗的開關。
* 默認是不開啟的,此時在加入事務的時候內層注解的一些設定相當於會被忽略。
*/
if (isValidateExistingTransaction()) {
// 如果自定義了隔離級別校驗與已有事務是否匹配。
if (definition.getIsolationLevel() != TransactionDefinition.ISOLATION_DEFAULT) {
Integer currentIsolationLevel = TransactionSynchronizationManager.getCurrentTransactionIsolationLevel();
// 如果已有事務隔離級別為null(說明是默認級別)或者已有事務隔離級別不等於當前事務定義的隔離級別拋出異常。
if (currentIsolationLevel == null || currentIsolationLevel != definition.getIsolationLevel()) {
Constants isoConstants = DefaultTransactionDefinition.constants;
throw new IllegalTransactionStateException("Participating transaction with definition [" +
definition + "] specifies isolation level which is incompatible with existing transaction: " +
(currentIsolationLevel != null ?
isoConstants.toCode(currentIsolationLevel, DefaultTransactionDefinition.PREFIX_ISOLATION) :
"(unknown)"));
}
}
// 如果已有事務為只讀,但本方法事務定義為非只讀,則拋出異常。
if (!definition.isReadOnly()) {
if (TransactionSynchronizationManager.isCurrentTransactionReadOnly()) {
throw new IllegalTransactionStateException("Participating transaction with definition [" +
definition + "] is not marked as read-only but existing transaction is");
}
}
}
boolean newSynchronization = (getTransactionSynchronization() != SYNCHRONIZATION_NEVER);
return prepareTransactionStatus(definition, transaction, false, newSynchronization, debugEnabled, null);
}
3.3 事務的掛起與恢復--suspend & resume方法
3.3.1 事務的掛起--suspend方法
/**
* 本方法做的事情主要就是抓取當前事務資源,事務基本信息以及事務回調transaction synchronization等塞到SuspendedResourcesHolder中返回。
*/
protected final SuspendedResourcesHolder suspend(Object transaction) throws TransactionException {
/*
* 在TransactionSynchronizationManager的JavaDoc上已經寫明了在需要進行transaction synchronization注冊的時候需要先檢查當前線程是否激活
* isSynchronizationActive是去讀TransactionSynchronizationManager中當前線程綁定的TransactionSynchronization集合是否為null。
*/
if (TransactionSynchronizationManager.isSynchronizationActive()) {
// 對所有本線程當前注冊的transaction synchronization調用suspend方法。
List<TransactionSynchronization> suspendedSynchronizations = doSuspendSynchronization();
try {
Object suspendedResources = null;
if (transaction != null) {
// 掛起當前事務,由具體的TxMgr子類實現。
suspendedResources = doSuspend(transaction);
}
/*
* 將當前線程綁定的事務名,是否只讀,隔離級別,是否有實際事務等信息抓取出來。
* 與剛才抓取出來的transaction synchronization集合一起包到SuspendedResourcesHolder中返回。
*/
String name = TransactionSynchronizationManager.getCurrentTransactionName();
TransactionSynchronizationManager.setCurrentTransactionName(null);
boolean readOnly = TransactionSynchronizationManager.isCurrentTransactionReadOnly();
TransactionSynchronizationManager.setCurrentTransactionReadOnly(false);
Integer isolationLevel = TransactionSynchronizationManager.getCurrentTransactionIsolationLevel();
TransactionSynchronizationManager.setCurrentTransactionIsolationLevel(null);
boolean wasActive = TransactionSynchronizationManager.isActualTransactionActive();
TransactionSynchronizationManager.setActualTransactionActive(false);
return new SuspendedResourcesHolder(
suspendedResources, suspendedSynchronizations, name, readOnly, isolationLevel, wasActive);
}
catch (RuntimeException ex) {
// 恢復transaction synchronization。
doResumeSynchronization(suspendedSynchronizations);
throw ex;
}
catch (Error err) {
// 恢復transaction synchronization。
doResumeSynchronization(suspendedSynchronizations);
throw err;
}
}
else if (transaction != null) {
// 掛起當前事務。
Object suspendedResources = doSuspend(transaction);
return new SuspendedResourcesHolder(suspendedResources);
}
else {
// 啥都沒有。
return null;
}
}
private List<TransactionSynchronization> doSuspendSynchronization() {
// getSynchronizations返回的是一個不可變的快照。
List<TransactionSynchronization> suspendedSynchronizations =
TransactionSynchronizationManager.getSynchronizations();
// 逐一掛起。
for (TransactionSynchronization synchronization : suspendedSynchronizations) {
synchronization.suspend();
}
// 清理之后除非重新init否則無法再register新的transaction synchronization了。
TransactionSynchronizationManager.clearSynchronization();
return suspendedSynchronizations;
}
3.3.2 事務的恢復-resume方法
protected final void resume(Object transaction, SuspendedResourcesHolder resourcesHolder) throws TransactionException {
if (resourcesHolder != null) {
Object suspendedResources = resourcesHolder.suspendedResources;
if (suspendedResources != null) {
// 恢復掛起的事務資源,由具體的TxMgr子類實現。
doResume(transaction, suspendedResources);
}
List<TransactionSynchronization> suspendedSynchronizations = resourcesHolder.suspendedSynchronizations;
if (suspendedSynchronizations != null) {
// 還原掛起的事務的一些信息。
TransactionSynchronizationManager.setActualTransactionActive(resourcesHolder.wasActive);
TransactionSynchronizationManager.setCurrentTransactionIsolationLevel(resourcesHolder.isolationLevel);
TransactionSynchronizationManager.setCurrentTransactionReadOnly(resourcesHolder.readOnly);
TransactionSynchronizationManager.setCurrentTransactionName(resourcesHolder.name);
// 恢復掛起的transaction syncrhonization。
doResumeSynchronization(suspendedSynchronizations);
}
}
}
/**
* 重新初始化當前線程維護的synchronization集合,逐一對這些synchronization調用resume方法並加入到集合中。
*/
private void doResumeSynchronization(List<TransactionSynchronization> suspendedSynchronizations) {
TransactionSynchronizationManager.initSynchronization();
for (TransactionSynchronization synchronization : suspendedSynchronizations) {
synchronization.resume();
TransactionSynchronizationManager.registerSynchronization(synchronization);
}
}
3.4 事務的提交與回滾--commit&rollback
commit與rollback兩個方法是PlatformTransactionManager接口兩個關鍵方法。
一般在事務切面增強的方法成功情況下會調用commit方法。在事務發生異常后,completeTransactionAfterThrowing方法會根據異常與事務規則是否匹配來決定是否需要回滾。如果需要回滾則調用rollback方法。
需要注意的是commit/rollback方法只是嘗試,Spring會根據事務狀態信息來具體處理,不代表一定會物理提交/回滾,Spring會在事務最外層邊界才可能觸發物理提交/回滾,甚至也有可能調用commit后發現需要rollback。
3.4.1 事務的提交--commit方法
public final void commit(TransactionStatus status) throws TransactionException {
// 如果事務已經完成了,則拋出異常。
if (status.isCompleted()) {
throw new IllegalTransactionStateException(
"Transaction is already completed - do not call commit or rollback more than once per transaction");
}
DefaultTransactionStatus defStatus = (DefaultTransactionStatus) status;
/*
* 通過前文的源碼分析可以知道無論事務怎么傳播,每一次進入事務攔截器,在進入業務方法之前都會把一個TransactionInfo對象塞到
* transactionInfoHolder這個線程本地變量中。而TransactionInfo包含了一個TransactionStatus對象。
* commit方法是在業務方法正常完成后調用的,所謂isLocalRollbackOnly就是讀當前TransactionStatus對象中的rollbackOnly標志位。
* 正如其名,它是一個局部的標志位,只有創建該status的那一層在業務方法執行完畢后會讀到本層status的這個局部標志位。
*
* 我們可以在用戶代碼(業務方法或者切面)中通過TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
* 來置當前事務層的status對象的rollbackOnly標志位為true以手動控制回滾。
*/
if (defStatus.isLocalRollbackOnly()) {
if (defStatus.isDebug()) {
logger.debug("Transactional code has requested rollback");
}
processRollback(defStatus);
return;
}
/*
* shouldCommitOnGlobalRollbackOnly默認實現是false。
* 這里判斷的語義就是如果發現事務被標記全局回滾並且在全局回滾標記情況下不應該提交事務的話,則進行回滾。
*
* 我們通常用的DataSourceTransactionManager對於isGlobalRollbackOnly的判斷是去讀status中transaction對象的ConnectionHolder的rollbackOnly標志位。
*/
if (!shouldCommitOnGlobalRollbackOnly() && defStatus.isGlobalRollbackOnly()) {
if (defStatus.isDebug()) {
logger.debug("Global transaction is marked as rollback-only but transactional code requested commit");
}
processRollback(defStatus);
/*
* isNewTransaction用來判斷是否是最外層(事務邊界)。
* 舉個例子:傳播行為REQUIRE方法調REQUIRE方法再調REQUIRE方法,第三個方法拋出異常,第二個方法捕獲,第一個方法走到這里會發現到了最外層事務邊界。
* 而failEarlyOnGlobalRollbackOnly是一個標志位,如果開啟了則會盡早拋出異常。
*
* 默認情況下failEarlyOnGlobalRollbackOnly開關是關閉的。這樣如果內層事務發生了異常,退棧到外層事務后,代碼走到這里回滾完后會拋出UnexpectedRollbackException。
*/
if (status.isNewTransaction() || isFailEarlyOnGlobalRollbackOnly()) {
throw new UnexpectedRollbackException(
"Transaction rolled back because it has been marked as rollback-only");
}
return;
}
processCommit(defStatus);
}
private void processCommit(DefaultTransactionStatus status) throws TransactionException {
try {
boolean beforeCompletionInvoked = false;
try {
// 鈎子函數,TxMgr子類可以覆蓋默認的空實現。
prepareForCommit(status);
// 回調transaction synchronization的beforeCommit方法。
triggerBeforeCommit(status);
// 回調transaction synchronization的beforeCompletion方法。
triggerBeforeCompletion(status);
beforeCompletionInvoked = true;
boolean globalRollbackOnly = false;
if (status.isNewTransaction() || isFailEarlyOnGlobalRollbackOnly()) {
globalRollbackOnly = status.isGlobalRollbackOnly();
}
if (status.hasSavepoint()) {
if (status.isDebug()) {
logger.debug("Releasing transaction savepoint");
}
status.releaseHeldSavepoint();
}
// 最外層事務邊界。
else if (status.isNewTransaction()) {
if (status.isDebug()) {
logger.debug("Initiating transaction commit");
}
// 由具體TxMgr子類實現。
doCommit(status);
}
/*
* 我們一般用的DataSourceTransactionManager是不會走到這里的。
* 因為默認shouldCommitOnGlobalRollbackOnly開關是關閉的,檢測到golobalRollbackOnly是不會走到processCommit方法的。
*
* 但shouldCommitOnGlobalRollbackOnly這個開關對於JtaTransactionManager來說是默認開啟的,這里主要是需要針對檢測到globalRollbackOnly但是doCommit沒有拋出異常的情況。
*/
if (globalRollbackOnly) {
throw new UnexpectedRollbackException(
"Transaction silently rolled back because it has been marked as rollback-only");
}
}
catch (UnexpectedRollbackException ex) {
// 回調transaction synchronization的afterCompletion方法。
triggerAfterCompletion(status, TransactionSynchronization.STATUS_ROLLED_BACK);
throw ex;
}
catch (TransactionException ex) {
// doCommit發生異常后,根據rollbackOnCommitFailure開關決定是否回滾,此開關默認關閉。
if (isRollbackOnCommitFailure()) {
doRollbackOnCommitException(status, ex);
}
else {
// 回調transaction synchronization的afterCompletion方法。
triggerAfterCompletion(status, TransactionSynchronization.STATUS_UNKNOWN);
}
throw ex;
}
catch (RuntimeException ex) {
if (!beforeCompletionInvoked) {
triggerBeforeCompletion(status);
}
doRollbackOnCommitException(status, ex);
throw ex;
}
catch (Error err) {
if (!beforeCompletionInvoked) {
triggerBeforeCompletion(status);
}
doRollbackOnCommitException(status, err);
throw err;
}
try {
// 回調transaction synchronization的afterCommit方法。
triggerAfterCommit(status);
}
finally {
// 回調transaction synchronization的afterCompletion方法。
triggerAfterCompletion(status, TransactionSynchronization.STATUS_COMMITTED);
}
}
finally {
// 后續工作:status標記completed,在最外層清空transaction synchronization集合,恢復掛起事務資源等等。
cleanupAfterCompletion(status);
}
}
3.4.2 事務的回滾--rollback方法
AbstractPlatformTransactionManager#rollback方法,否則調用commit方法。
public final void rollback(TransactionStatus status) throws TransactionException {
if (status.isCompleted()) {
throw new IllegalTransactionStateException(
"Transaction is already completed - do not call commit or rollback more than once per transaction");
}
DefaultTransactionStatus defStatus = (DefaultTransactionStatus) status;
processRollback(defStatus);
}
private void processRollback(DefaultTransactionStatus status) {
try {
try {
// 回調transaction synchronization對象的beforeCompletion方法。
triggerBeforeCompletion(status);
if (status.hasSavepoint()) {
if (status.isDebug()) {
logger.debug("Rolling back transaction to savepoint");
}
status.rollbackToHeldSavepoint();
}
// 在最外層事務邊界進行回滾。
else if (status.isNewTransaction()) {
if (status.isDebug()) {
logger.debug("Initiating transaction rollback");
}
// 由具體TxMgr子類實現回滾。
doRollback(status);
}
else if (status.hasTransaction()) {
/*
* 內層事務被標記為rollBackOnly或者globalRollbackOnParticipationFailure開關開啟時,給當前事務標記需要回滾。
*
* 如果內層事務顯式打上了rollBackOnly的標記,最終全事務一定是回滾掉的。
*
* 但如果沒有被打上rollBackOnly標記,則globalRollbackOnParticipationFailure開關就很重要了。
* globalRollbackOnParticipationFailure開關默認是開啟的,也就是說內層事務掛了,最終的結果只能是全事務回滾。
* 但如果globalRollbackOnParticipationFailure開關被關閉的話,內層事務掛了,外層事務業務方法中可以根據情況控制是否回滾。
*/
if (status.isLocalRollbackOnly() || isGlobalRollbackOnParticipationFailure()) {
if (status.isDebug()) {
logger.debug("Participating transaction failed - marking existing transaction as rollback-only");
}
// 由具體TxMgr子類實現回滾。
doSetRollbackOnly(status);
}
else {
if (status.isDebug()) {
logger.debug("Participating transaction failed - letting transaction originator decide on rollback");
}
}
}
else {
logger.debug("Should roll back transaction but cannot - no transaction available");
}
}
catch (RuntimeException ex) {
triggerAfterCompletion(status, TransactionSynchronization.STATUS_UNKNOWN);
throw ex;
}
catch (Error err) {
triggerAfterCompletion(status, TransactionSynchronization.STATUS_UNKNOWN);
throw err;
}
// 回調transaction synchronization對象的afterCompletion方法。
triggerAfterCompletion(status, TransactionSynchronization.STATUS_ROLLED_BACK);
}
finally {
// 后續工作:status標記completed,在最外層清空transaction synchronization集合,恢復掛起事務資源等等。
cleanupAfterCompletion(status);
}
}
4. 案例分析
4.1 進不了事務
這個問題也是使用Spring聲明式事務很常見的問題。
首先保證配置都是正確的,並且開啟了Spring事務(比如@EnableTransactionManagement
)。
要明白進事務的本質就是進到事務切面的代理方法中,最常見的是同一個類的非事務方法調用一個加了事務注解的方法沒進入事務。
我們以cglib代理為例,由於Spring的對於cglib AOP代理的實現,進入被代理方法的時候實際上已經離開了“代理這一層殼子”,可以認為代碼走到的是一個朴素的bean,調用同一個bean中方法自然與代理沒有半毛錢關系了。
一般對於聲明式事務都是以調用另一個類的加了@Transactional
注解的public方法作為入口的。
4.2 異常捕捉后沒返回值並且拋出UnexpectedRollbackException
這個案例是工作中同事讓我看的一個線上真實案例。
下面是我將原問題用簡化代碼的形式模擬業務場景的簡單demo,只有core Java代碼和Spring相關注解的可運行代碼。
首先創建一個表。
create table t (value varchar(20));
新建三個類, ServiceA, ServiceB和Response。
/**
* 模擬加了事務注解的外層service。
*/
@Service
public class ServiceA {
@Autowired
private DataSource dataSource;
@Autowired
private ServiceB serviceB;
@Transactional
public Response insert() {
executeSql("insert into t select 'serviceA 開始'");
try {
serviceB.insert();
} catch (Exception e) {
System.out.println("serviceB#insert掛了,原因: " + e);
return Response.FAIL;
}
return Response.SUCC;
}
private void executeSql(String sql) {
Connection connection = DataSourceUtils.getConnection(dataSource);
try {
connection.createStatement().execute(sql);
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
}
/**
* 被外層ServiceA調用的ServiceB,拋出異常模擬來模擬掛了的情況。
*/
@Service
public class ServiceB {
@Autowired
private DataSource dataSource;
// 用於控制是否模擬insert方法掛了的情況。
private boolean flag = true;
@Transactional
public void insert() {
executeSql("insert into t select '這里是ServiceB掛之前'");
if (true) {
throw new RuntimeException("模擬內層事務某條語句掛了的情況");
}
executeSql("insert into t select '這里是ServiceB掛之后'");
}
private void executeSql(String sql) {
Connection connection = DataSourceUtils.getConnection(dataSource);
try {
connection.createStatement().execute(sql);
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
}
/**
* Response類。
*/
public class Response {
public static final Response SUCC = new Response();
public static final Response FAIL = new Response();
}
如果調用ServiceA#insert可以觀察到控制台輸出
serviceB#insert掛了,原因: java.lang.RuntimeException: 模擬內層事務掛了的情況
的報錯日志。
但ServiceA#insert沒有返回Response.FAIL並且Spring還拋出了異常
org.springframework.transaction.UnexpectedRollbackException: Transaction rolled back because it has been marked as rollback-only
觀察MySQL通用日志,結果如下:
2017-10-03T12:46:21.791139Z 382 Connect root@localhost on test using SSL/TLS
2017-10-03T12:46:21.796530Z 382 Query /* mysql-connector-java-5.1.42 ( Revision: 1f61b0b0270d9844b006572ba4e77f19c0f230d4 ) */SELECT @@session.auto_increment_increment AS auto_increment_increment, @@character_set_client AS character_set_client, @@character_set_connection AS character_set_connection, @@character_set_results AS character_set_results, @@character_set_server AS character_set_server, @@init_connect AS init_connect, @@interactive_timeout AS interactive_timeout, @@license AS license, @@lower_case_table_names AS lower_case_table_names, @@max_allowed_packet AS max_allowed_packet, @@net_buffer_length AS net_buffer_length, @@net_write_timeout AS net_write_timeout, @@query_cache_size AS query_cache_size, @@query_cache_type AS query_cache_type, @@sql_mode AS sql_mode, @@system_time_zone AS system_time_zone, @@time_zone AS time_zone, @@tx_isolation AS tx_isolation, @@wait_timeout AS wait_timeout
2017-10-03T12:46:21.821206Z 382 Query SET character_set_results = NULL
2017-10-03T12:46:21.821757Z 382 Query SET autocommit=1
2017-10-03T12:46:21.826013Z 382 Query SET autocommit=0
2017-10-03T12:46:21.837792Z 382 Query select @@session.tx_read_only
2017-10-03T12:46:21.840326Z 382 Query insert into t select 'serviceA 開始'
2017-10-03T12:46:21.850478Z 382 Query select @@session.tx_read_only
2017-10-03T12:46:21.853736Z 382 Query insert into t select '這里是ServiceB掛之前'
2017-10-03T12:46:21.854520Z 382 Query rollback
2017-10-03T12:46:21.855058Z 382 Query SET autocommit=1
2017-10-03T12:46:21.855514Z 382 Query select @@session.tx_read_only
2017-10-03T12:46:21.856284Z 382 Quit
可以很清楚的看到整個事務最終被回滾掉了, ServiceB#insert並沒有執行insert into t select '這里是ServiceB掛之后'
。
其實對於Spring事務來說,這樣的結果是正確的,但對於開發者來說,這個結果確實看似有些“不能理解”。
我們不妨來分析一下原因:
首先ServiceB#insert本身是直接拋出RuntimeException的,那么退棧到事務切面后,事務切面會發現需要回滾但因為ServiceB#insert還不是事務的最外層邊界,所以在AbstractPlatformTransactionManager#processRollback方法僅僅會調用doSetRollbackOnly(status);
,子類DataSourceTransactionManager會拿出TxStatus中的transaction對象打上回滾標記,具體來說就是transaction對象(對於DataSourceTransactionManager來說類型是DataSourceTransactionObject)會取出ConnectionHolder,調用setRollbackOnly。我們知道這樣就相當於標記是一個全局的標記了,因為只要是隸屬於同一個物理事務的Spring事務都能夠讀到同一個ConnectionHolder。
好了,接下來到了ServiceA在catch塊准備返回Response.FAIL的時候,退棧到事務切面,在AbstractPlatformTransactionManager#commit方法讀到if(!shouldCommitOnGlobalRollbackOnly() && defStatus.isGlobalRollbackOnly())
條件成立,接下來調用processRollback
,由於在事務最外層邊界會物理回滾掉,並且也正是到了事務最外層邊界,Spring拋出UnexpectedRollbackException。
至此原因已經分析完畢。
那么問題怎么解決呢,這個問題有好幾種解決辦法,但是得根據具體情況決定。
第一種: 根據實際代碼與業務情況看看ServiceB#insert是否有必要加事務。如果不加事務的話,其實就事務角度來分析,ServiceB#insert相當於被內聯到了ServiceA#insert中。就上面的示例而言,如果我們把ServiceB#insert的事務注解拿掉,則事務是可以順利提交的,Spring也不會拋出UnexpectedRollbackException。但是ServiceB#insert實際上並沒有完整執行,所以這樣的解決思路很容易導致出現不完整的臟數據。當然還是要看具體業務需求,如果可以接受的話也無所謂。
第二種:手動控制是否回滾。如果不能接受ServiceB掛的話,可以在catch塊里加上TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
用於顯式控制回滾。這樣Spring就明白你自己要求回滾事務,而不是unexpected了。Spring也不會拋出UnexpectedRollbackException了。那么如果在ServiceA中捕獲到異常,真的就是不想回滾,即便ServiceA發生了異常,也想要最終提交整個事務呢?如果有這樣的需求的話,可以給TxMgr配置一個參數setGlobalRollbackOnParticipationFailure(false);
。這樣只要沒有顯式在代碼里通過給TxStatus設置回滾標記,Spring在內層事務掛了的情況,不會去給該事務打上需要回滾的標記。換句話說,這時候處於完全手動擋來控制內層事務掛了的情況到底整個事務的提交/回滾了。
第三種:繼續向上拋出異常。可以考慮繼續向上拋出異常,一般在web項目都會配置一個大的異常處理切面,統一返回失敗Response。Service層不需要考慮Response。因此可以考慮重構掉ServiceA的方法簽名,改為void,不關心Response。也不用去捕捉ServiceB的異常了。
5. 總結
本文主要以Spring聲明式事務為切入點,介紹了Spring事務的實現原理與源碼。由於在前文的套路簡介中也以文字描述了Spring聲明式事務的大致套路,這里不再贅述。這里順便提一句,閱讀Spring源碼除了看注釋,調試代碼,其實很個東西很容易忽視——Spring打印log的語句,那些語句的內容很多時候都是很有啟發的,會讓你突然明白整個分支邏輯到底是想干什么。
這里再整理一下整個事務切面的流程:
- 調用被事務增強的方法,進入事務切面。
- 解析事務屬性,調用具體的TxMgr負責生成TxStatus。
- 如果當前已經有事務,進入step 5。
- 根據事務傳播行為,校驗是否需要拋出異常(如MANDATORY),或者掛起事務信息(由於沒有真正的物理事務,所以沒有需要掛起的事務資源)並創建事務(REQUIRED, REQUIREDS_NEW, NESTED等),又或者創建一個空事務(SUPPORTS, NOT_SUPPORTED, NEVER等)。進入step 6。
- 根據事務傳播行為,拋出異常(NEVER),或者掛起事務資源與信息並根據情況決定是否創建事務(NOT_SUPPORTED, REQUIRES_NEW等),或者根據情況處理嵌套事務(NESTED)或者加入已有事務(SUPPORTS, REQUIRED, MANDATORY等)。
- 生成TxInfo並綁定到線程。
- 回調MethodInterceptor,事務切面前置工作至此完成。
- 如果發生異常進入step 10。
- 根據TxStatus是否被標記回滾,事務本身是否被標記回滾等決定是否要進入處理回滾的邏輯。只有在某事務最外層邊界,才可能進行物理提交/回滾,否則最多就在需要回滾的情況下給事務打標需要回滾,不會有真正的動作。並且一般情況下,如果在事務最外邊界發現事務需要回滾,會拋出UnexpectedRollbackException。其余情況進入step 11。
- 根據異常情況與事務屬性判斷異常是否需要進入處理回滾的邏輯還是進入處理提交的邏輯。如果需要回滾則根據是否到了某事務最外層邊界決定是否進行物理回滾,否則給事務打標需要回滾。如果進入處理提交邏輯則同樣只有在事務最外層邊界才可能有真正的物理提交動作。
- 無論是否發生異常,都會恢復TxInfo為前一個事務的TxInfo(類似於彈棧)。
下面總結一下Spring事務控制的一些重要參數。掌握這些參數可以更靈活地配置TxMgr。
幾個重要參數
Spring事務在控制提交和回滾中用了不少判斷條件,了解其中一些關鍵參數的含義對debug問題很有幫助。下文描述的一些控制參數的默認是指在AbstractPlatformTransactionManager中的默認值。
- transactionSynchronization
在Spring事務的源碼實現中,將synchronization與transaction的邊界划分分離開來。可以在DefaultTransactionStatus類中看到newTransaction和newSynchronization是兩個獨立的邊界控制參數。它有三種取值:- SYNCHRONIZATION_ALWAYS(0)
這是默認取值。在默認取值的情況下,在任何情況下都會開啟資源與信息的同步維護。舉個例子:這樣的話即便我們進入了NOT_SUPPORTED傳播行為的方法,通過TransactionSynchronizationManager.getCurrentTransactionName()
也能拿到當前的事務名(為此NOT_SUPPORTED事務方法名)。同理,在沒有事務的情況下進入SUPPORTS傳播行為的方法也能夠讀到當前事務名currentTransactionName。 - SYNCHRONIZATION_ON_ACTUAL_TRANSACTION(1)
此取值保證只維護存在實際物理事務的事務方法的信息與資源。舉個例子:在沒有事務情況下進入SUPPORTS傳播行為的方法,通過TransactionSynchronizationManager拿到的currentTransactionName一定是null。但如果取值為SYNCHRONIZATION_ALWAYS就會不同,如果不存在實際物理事務則拿到的事務名是SUPPORTS方法名,如果存在實際物理事務則能夠拿到創建該實際物理事務的方法名。 - SYNCHRONIZATION_NEVER(2)
這個取值會導致在任何情況下TxStats的isNewSynchronization方法永遠返回false。這樣的話像currentTransactionName, currentTransactionIsolationLevel以及transaction synchronization都是沒辦法與事務生命周期同步維護的。
- SYNCHRONIZATION_ALWAYS(0)
- nestedTransactionAllowed
默認為false。這個和具體TxMgr子類實現有關。這個開關的作用是是否允許嵌套事務,如果不允許的話如果有NESTED傳播行為事務試圖加入已有事務會拋出NestedTransactionNotSupportedException。 - validateExistingTransaction
默認為false。也就是在內層事務(比如REQUIRED/SUPPORTS傳播行為)加入外層事務的時候不會做校驗。如果開啟的話會去校驗內外層事務隔離級別,是否只讀等。如果有沖突會拋出IllegalTransactionStateException。 - globalRollbackOnParticipationFailure
默認為true。在內層事務(比如REQUIRED/SUPPORTS傳播行為)加入外層事務后,如果內層事務掛了,會給事務打上回滾的全局標記,這個事務最終只能被回滾而不能被提交。
如果把開關置為false的話,我們在應用層捕捉到內層事務的異常后,可以自己決定到底要不要回滾(要回滾的話具體實現可以繼續拋異常或者給事務狀態setRollbackOnly手動標記回滾)。 - failEarlyOnGlobalRollbackOnly
默認為false。也就是在最外層事務邊界嘗試提交時發現事務需要回滾,在物理回滾完后,會拋出UnexpectedRollbackException,而內層事務發現事務需要回滾時,僅僅只會調用processRollback給事務標記回滾。
如果這個開關打開的話,在某個內層事務嘗試提交時發現事務需要回滾后,在調用processRollback給事務打上回滾的全局標記后,就會直接立刻拋出UnexpectedRollbackException。外層事務代碼只要沒捕獲到異常,后續代碼就不會繼續執行,退棧到事務切面中進行物理回滾。
這個開關打開的一個好處是可以避免內層事務掛了,外層捕獲異常,但我們期望整個事務最終還是需要回滾的情況下,避免外層事務繼續執行語句,否則可能會執行了一大堆的sql最終還是回滾。 - rollbackOnCommitFailure
默認為false。如果開啟的話在事務真正物理提交doCommit失敗后會進行回滾。 - shouldCommitOnGlobalRollbackOnly()
與其他參數不同, shouldCommitOnGlobalRollbackOnly是作為一個boolean方法可以被TxMgr具體實現類覆蓋的。默認為false。它用於控制一個事務對象被打上需要回滾的全局標記后,是否需要回滾。如果置為true的話,會去嘗試提交事務。需要注意的是即便如此,如果TxMgr子類的doCommit方法實現中沒有一些對這種被打上回滾標記的事務的處理邏輯,例如拋出異常之類的,Spring最終還是會拋出UnexpectedRollbackException。我們常見使用的DataSourceTransactionManager一般不會去把這個參數改為true。
6. 一個小插曲
在閱讀源碼的過程中,避免不了閱讀Java Doc注釋,發現有一處寫了個"PROPAGATION_REQUIRES",按照TransactionDefinition中定義的常量應該是"PROPAGATION_REQUIRED"以及"PROPAGATION_REQUIRES_NEW",前者是"REQUIRED"而不是"REQUIRES"。我用正則在Spring全源碼中搜了一下把"PROPAGATION_REQUIRED"寫成"PROPAGATION_REQUIRES"的有三處,然后給Spring發了一個PR。已經合入Spring主干。
7. 參考
Spring 4.3.5 源碼
StackOverflow