問題
- 單個事務會發(fā)生rollback-only嘛阐污?嵌套事務就一定會有這個rollback-only問題嘛咱圆?
- 這個rollback-only是什么?為什么會拋出這么個異常隘膘?
- 這個rollback-only異常會有哪些實際性危害弯菊?
發(fā)生場景
之前項目線上發(fā)生報錯:“org.springframework.transaction.UnexpectedRollbackException: Transaction rolled back because it has been marked as rollback-only”踱阿∪砩啵看到了rollback-only這幾個字佛点,大概已經(jīng)知道了是什么問題。
出錯原因
錯誤代碼舉例如下:
@Service
public class BillService {
@Resource
private BillMapper billMapper;
@Resource
private BillFeeService billFeeService;
@Transactional(rollbackFor = Exception.class)
public int updateStatus(String billId, BillStatusEnum billStatus) {
billMapper.updateStatus(billId, billStatus);
try {
// 內部事務
billFeeService.updateStatus(billId, billStatus);
} catch (Exception ex) {
// 將異常吞了 或者根據(jù)異常又做了其他事
log.error("打印日志");
}
// ...其他事務
}
}
@Service
public class BillFeeService {
@Resource
private BillFeeMapper billFeeMapper;
@Transactional(rollbackFor = Exception.class)
public int updateStatus(String billId, BillStatusEnum billFee) {
// 模擬拋異常
}
問題分析
上述例子使用注解方式申明事務鸳玩,而申明事務通過Spring AOP增強實現(xiàn)不跟。無論是 JDK 動態(tài)代理米碰,還是 CGLIB 動態(tài)代理吕座,都會保留了原類和增強代理類( IOC容器初始化Bean 時生成,AbstractAutoProxyCreator.postProcessAfterInitialization方法使用SingletonTargetSource來包裝bean)兩個類菱蔬。代理類仍然持有原類(CGLIB 采用繼承的方式拴泌,JDK 動態(tài)代理采用反射的方式)惊橱,把原類包裝了起來税朴,原類并沒有被增強家制。當外部調用方法時颤殴,代理類判斷這個方法是否進行事務攔截鼻忠,從而會調用到TransactionIntercptor.invoke()方法帖蔓。
AOP切入方法流程示意圖:
再看TransactionInterceptor的圖表關系:
@Override
@Nullable
public Object invoke(MethodInvocation invocation) throws Throwable {
Class<?> targetClass = (invocation.getThis() != null ? AopUtils.getTargetClass(invocation.getThis()) : null);
return invokeWithinTransaction(invocation.getMethod(), targetClass, invocation::proceed);
}
從上圖可以看出TransactionIntercptor是繼承了TransactionAspectSupport類 澈侠,實現(xiàn)了MethodInterceptor接口埋酬。事務真正處理邏輯在父類的invokeWithinTransaction方法中写妥。
而invokeWithinTransaction方法中主要做的流程:
- 獲取事務的屬性
- 加載配置中配的transactionManager
-
不同的事務機制使用不同的事務,如下:
image.png
- 在目標方法執(zhí)行前獲取事務并收集事務信息
- 執(zhí)行需要增強的目標方法
- 出現(xiàn)異常,嘗試異常處理
- 提交事務前的事務信息清除
- 提交事務
invokeWithinTransaction流程示意圖:
源碼再探
protected Object invokeWithinTransaction(Method method, @Nullable Class<?> targetClass,
final InvocationCallback invocation) throws Throwable {
// 獲取事務屬性
TransactionAttributeSource tas = getTransactionAttributeSource();
final TransactionAttribute txAttr = (tas != null ? tas.getTransactionAttribute(method, targetClass) : null);
// 根據(jù)事務屬性確定對應的事務
final TransactionManager tm = determineTransactionManager(txAttr);
// 這里是判斷是反應式事務
if (this.reactiveAdapterRegistry != null && tm instanceof ReactiveTransactionManager) {
// ...
}
PlatformTransactionManager ptm = asPlatformTransactionManager(tm);
// 獲取方法唯一標識呼猪,這里的 descriptor就是在 獲取事務屬性txAttr時 設置進去的.
final String joinpointIdentification = methodIdentification(method, targetClass, txAttr);
/**
* 申明式事務
* 我們一般申明式事務定義的是DataSourceTransactionManager
**/
if (txAttr == null || !(ptm instanceof CallbackPreferringPlatformTransactionManager)) {
// 創(chuàng)建 TransactionInfo
TransactionInfo txInfo = createTransactionIfNecessary(ptm, txAttr, joinpointIdentification);
Object retVal;
try {
// 執(zhí)行要被增強的方法
retVal = invocation.proceedWithInvocation();
}
catch (Throwable ex) {
// target invocation exception
// 異乘尉啵回滾
completeTransactionAfterThrowing(txInfo, ex);
throw ex;
}
finally {
// 清除當前事務信息谚赎,恢復舊事務
cleanupTransactionInfo(txInfo);
}
if (retVal != null && vavrPresent && VavrDelegate.isVavrTry(retVal)) {
// Set rollback-only in case of Vavr failure matching our rollback rules...
TransactionStatus status = txInfo.getTransactionStatus();
if (status != null && txAttr != null) {
retVal = VavrDelegate.evaluateTryFailure(retVal, txAttr, status);
}
}
// 提交
commitTransactionAfterReturning(txInfo);
return retVal;
}
else {
Object result;
final ThrowableHolder throwableHolder = new ThrowableHolder();
// ...
}
}
再看createTransactionIfNecessary
方法中創(chuàng)建事務信息動作過程壶唤。
protected TransactionInfo createTransactionIfNecessary(@Nullable PlatformTransactionManager tm,
@Nullable TransactionAttribute txAttr, final String joinpointIdentification) {
/**
如果沒有指定名稱棕所,則將方法標識應用為事務名稱琳省,就是之前設置的 class.method
**/
if (txAttr != null && txAttr.getName() == null) {
txAttr = new DelegatingTransactionAttribute(txAttr) {
@Override
public String getName() {
return joinpointIdentification;
}
};
}
// 事務狀態(tài)
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");
}
}
}
/**
* 這個方法會將新生成TransactionInfo并放入放入ThreadLocal中這樣就便于后續(xù)的還原,提交击费,回滾等一系列操作.
* private static final ThreadLocal<TransactionInfo> transactionInfoHolder =
* new NamedThreadLocal<>("Current aspect-driven transaction");
*/
return prepareTransactionInfo(tm, txAttr, joinpointIdentification, status);
}
AbstractPlatformTransactionManager#getTransaction:
public final TransactionStatus getTransaction(@Nullable TransactionDefinition definition)
throws TransactionException {
// Use defaults if no transaction definition given.
// TransactionDefinition就是TransactionAttribute蔫巩,就是@Transactional注解信息
// 表示一個事務的定義批幌,通過@Transactional注解也就是在定義一個事務
TransactionDefinition def = (definition != null ? definition : TransactionDefinition.withDefaults());
// 獲取一個事務對象嗓节,每次都會生成一個DataSourceTransactionObject拦宣,而重點是這個對象中是否已經(jīng)存在一個數(shù)據(jù)庫連接
Object transaction = doGetTransaction();
boolean debugEnabled = logger.isDebugEnabled();
// 判斷當前線程是否存在事務,判讀依據(jù)為當前線程記錄的連接不為空且連接中(connectionHolder)中的transactionActive屬性不為空
if (isExistingTransaction(transaction)) {
// 已存在事務的情況下绸罗,按不同的傳播級別進行處理
return handleExistingTransaction(def, transaction, debugEnabled);
}
// 事務超時驗證 timeout如果為-1珊蟀,表示沒有時間限制
if (def.getTimeout() < TransactionDefinition.TIMEOUT_DEFAULT) {
throw new InvalidTimeoutException("Invalid transaction timeout", def.getTimeout());
}
// 當前線程還不存在事務
// No existing transaction found -> check propagation behavior to find out how to proceed.
if (def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_MANDATORY) {
throw new IllegalTransactionStateException(
"No existing transaction found for transaction marked with propagation 'mandatory'");
}
// 這3個都會新建事務
else if (def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRED ||
def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRES_NEW ||
def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NESTED) {
// 掛起
SuspendedResourcesHolder suspendedResources = suspend(null);
if (debugEnabled) {
logger.debug("Creating new transaction with name [" + def.getName() + "]: " + def);
}
try {
boolean newSynchronization = (getTransactionSynchronization() != SYNCHRONIZATION_NEVER);
// 新建事務
DefaultTransactionStatus status = newTransactionStatus(
def, transaction, true, newSynchronization, debugEnabled, suspendedResources);
// 開啟事務育灸,包括設置ConnectionHolder昵宇、隔離級別瓦哎、timout如果是新連接,綁定到當前線程
doBegin(transaction, def);
// 初始化TransactionSynchronizationManager中的屬性
prepareSynchronization(status, def);
return status;
}
catch (RuntimeException | Error ex) {
resume(null, suspendedResources);
throw ex;
}
}
else {
// 不會doBegin割岛,不會真的開啟事務蜂桶,也就是不會把Connection的autoCommit設置為false也切,sql沒有在事務中執(zhí)行
// Create "empty" transaction: no actual transaction, but potentially synchronization.
if (def.getIsolationLevel() != TransactionDefinition.ISOLATION_DEFAULT && logger.isWarnEnabled()) {
logger.warn("Custom isolation level specified but no actual transaction initiated; " +
"isolation level will effectively be ignored: " + def);
}
//
boolean newSynchronization = (getTransactionSynchronization() == SYNCHRONIZATION_ALWAYS);
return prepareTransactionStatus(def, null, true, newSynchronization, debugEnabled, null);
}
}
completeTransactionAfterThrowing
方法在拋出異常處理的動作。
- 依據(jù)注解上的rollbackFor = Exception.class 屬性規(guī)則進行匹配费坊,看拋出的異常是否是配置的異常實例旬痹。
- 第一條不滿足两残,會去匹配默認的事務屬性,看是否是RuntimeException || Error錯誤的實例進行回滾
protected void completeTransactionAfterThrowing(@Nullable TransactionInfo txInfo, Throwable ex) {
if (txInfo != null && txInfo.getTransactionStatus() != null) {
if (logger.isTraceEnabled()) {
logger.trace("Completing transaction for [" + txInfo.getJoinpointIdentification() +
"] after exception: " + ex);
}
// 檢驗異常ex instanceof RuntimeException || ex instanceof Error
if (txInfo.transactionAttribute != null && txInfo.transactionAttribute.rollbackOn(ex)) {
try {
// 回滾事務
txInfo.getTransactionManager().rollback(txInfo.getTransactionStatus());
}
catch (TransactionSystemException ex2) {
logger.error("Application exception overridden by rollback exception", ex);
ex2.initApplicationException(ex);
throw ex2;
}
catch (RuntimeException | Error ex2) {
logger.error("Application exception overridden by rollback exception", ex);
throw ex2;
}
}
else {
try {
// 會嘗試提交事務,里面也有rollback-only屬性校驗
txInfo.getTransactionManager().commit(txInfo.getTransactionStatus());
}
catch (TransactionSystemException ex2) {
logger.error("Application exception overridden by commit exception", ex);
ex2.initApplicationException(ex);
throw ex2;
}
catch (RuntimeException | Error ex2) {
logger.error("Application exception overridden by commit exception", ex);
throw ex2;
}
}
}
AbsTractPlatformTransactionManager:
private void processRollback(DefaultTransactionStatus status, boolean unexpected)
{
try {
boolean unexpectedRollback = unexpected;
try {
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");
}
doRollback(status);
}
else {
// 有原有事務的話會去設置當前事務狀態(tài)
if (status.hasTransaction()) {
// isGlobalRollbackOnParticipationFailure 在參與事務失敗后將現(xiàn)有事務全局標記為rollback-only
if (status.isLocalRollbackOnly() || isGlobalRollbackOnParticipationFailure()) {
if (status.isDebug()) {
logger.debug("Participating transaction failed - marking existing transaction as rollback-only");
}
// 設置當前TransactionStatus的rollback-only狀態(tài)為true
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");
}
// Unexpected rollback only matters here if we're asked to fail early
if (!isFailEarlyOnGlobalRollbackOnly()) {
unexpectedRollback = false;
}
}
}
catch (RuntimeException | Error ex) {
triggerAfterCompletion(status, TransactionSynchronization.STATUS_UNKNOWN);
throw ex;
}
triggerAfterCompletion(status, TransactionSynchronization.STATUS_ROLLED_BACK);
// 這兒拋出我們最早展示的錯誤
if (unexpectedRollback) {
throw new UnexpectedRollbackException(
"Transaction rolled back because it has been marked as rollback-only");
}
}
finally {
// 最終清除事務信息
cleanupAfterCompletion(status);
}
}
}
最后看commitTransactionAfterReturning
方法去提交事務處理的過程健芭。
// 真正commit提交處理的方法
AbstractPlatformTransactionManager:
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;
// 這個是本地代碼手動設置rollback-only 不想以拋異常的方式回滾事務,就可以設置這個屬性
if (defStatus.isLocalRollbackOnly()) {
if (defStatus.isDebug()) {
logger.debug("Transactional code has requested rollback");
}
processRollback(defStatus, false);
return;
}
// 在最后提交的時候會校驗整個事務鏈中是否有 rollback-only標識
if (!shouldCommitOnGlobalRollbackOnly() && defStatus.isGlobalRollbackOnly()) {
if (defStatus.isDebug()) {
logger.debug("Global transaction is marked as rollback-only but transactional code requested commit");
}
processRollback(defStatus, true);
return;
}
processCommit(defStatus);
}
private void processCommit(DefaultTransactionStatus status) throws TransactionException {
try {
boolean beforeCompletionInvoked = false;
try {
boolean unexpectedRollback = false;
prepareForCommit(status);
triggerBeforeCommit(status);
triggerBeforeCompletion(status);
beforeCompletionInvoked = true;
// 事務是否存在回滾保存點
if (status.hasSavepoint()) {
if (status.isDebug()) {
logger.debug("Releasing transaction savepoint");
}
unexpectedRollback = status.isGlobalRollbackOnly();
status.releaseHeldSavepoint();
}
// 是否新事務
else if (status.isNewTransaction()) {
if (status.isDebug()) {
logger.debug("Initiating transaction commit");
}
unexpectedRollback = status.isGlobalRollbackOnly();
doCommit(status);
}
// 這里再次對rollback-only 校驗,重置unexpectedRollback狀態(tài)
else if (isFailEarlyOnGlobalRollbackOnly()) {
unexpectedRollback = status.isGlobalRollbackOnly();
}
// 這兒也會拋出UnexpectedRollbackException
if (unexpectedRollback) {
throw new UnexpectedRollbackException(
"Transaction silently rolled back because it has been marked as rollback-only");
}
}
catch (UnexpectedRollbackException ex) {
}
總結
異常出現(xiàn)必要因素
- 出現(xiàn)嵌套事務并且所有事務的傳播機制為 PROPAGATION_REQUIRED
- 內嵌事務在外層被try catch后沒再往出拋
發(fā)生問題的根本原因
存在嵌套事務的場景下谴麦,內嵌事務傳播機制會影響到事務的整個提交细移,比如默認的PROPAGATION_REQUIRED機制 在已有事務的情況下會加入原先事務熊锭,形成了一個大的事務鏈碗殷。它們本應該是一體的,所有的提交應該一起成功或失敗代乃。但由于內部事務發(fā)生了異常而且還沒拋出來,這就不符合事務的特性了原茅。所以在嵌套事務中堕仔,最外層的commit會兜底去校驗多層嵌套事務鏈中是否存在rollback-only狀態(tài)來標識這個事務鏈中發(fā)生了錯誤摩骨,來保證事務的原子性。
解決應對方案
- 內嵌事務傳播機制可修改 PROPAGATION_REQUIRED_NEW 昌罩,需注意:此傳播方式會新建一個事務灾馒,不會加入原有的事務中你虹。
- 傳播機制可修改 PROPAGATION_NESTED彤避,會創(chuàng)建一個保存點,在內嵌事務發(fā)生異常后會回滾到保存點(詳細:AbstractPlatformTransactionManager#getTransaction#handleExistingTransaction)
- 檢查多重嵌套事務代碼中的try catch董饰,很容易忽略卒暂。
- 盡量避免多重嵌套事務娄帖,可整合為單個大事務去提交近速。
補充
事務的傳播機制影響: