上一篇分析了事務如何在方法入棧時創建/加入,本文聚焦方法出棧時的 事務邊界控制:提交、回滾、保存點釋放、線程上下文清理。核心入口仍是 TransactionInterceptor.invoke()。
public Object invoke(MethodInvocation invocation) throws Throwable {
Class targetClass = (invocation.getThis() != null) ? invocation.getThis().getClass() : null;
TransactionInfo txInfo = createTransactionIfNecessary(invocation.getMethod(), targetClass);
Object retVal = null;
try {
retVal = invocation.proceed(); // 執行業務方法 + 後續攔截器
} catch (Throwable ex) {
completeTransactionAfterThrowing(txInfo, ex); // 異常路徑:決定回滾/提交
throw ex;
} finally {
cleanupTransactionInfo(txInfo); // 必執行:清理線程綁定的 TransactionInfo
}
commitTransactionAfterReturning(txInfo); // 正常路徑:提交事務
return retVal;
}
關鍵觀察:cleanupTransactionInfo() 在 finally 塊中,保證無論正常/異常/返回,線程綁定的 TransactionInfo 棧都會正確彈出。
private void bindToThread() {
oldTransactionInfo = (TransactionInfo) currentTransactionInfo.get(); // 保存舊棧頂
currentTransactionInfo.set(this); // 當前棧頂 = this
}
currentTransactionInfo 是 ThreadLocal。
protected void cleanupTransactionInfo(TransactionInfo txInfo) {
if (txInfo == null) return;
if (txInfo.oldTransactionInfo != null) {
currentTransactionInfo.set(txInfo.oldTransactionInfo); // 恢復父事務上下文
} else {
currentTransactionInfo.remove(); // 棧空,移除 ThreadLocal
}
}
TxInfo_A,old=null,綁定線程。TxInfo_B,old=TxInfo_A,綁定線程(遮蓋 A)。cleanup(TxInfo_B) → 恢復線程綁定為 TxInfo_A。不提交事務(見下文)。cleanup(TxInfo_A) → 移除線程綁定。提交事務。這完美實現了 事務上下文的巢狀棧語義,且與事務實體(Connection/Session)分離——TransactionInfo 只是輔助類,真實事務狀態在 TransactionStatus 中。
protected void commitTransactionAfterReturning(TransactionInfo txInfo) {
if (txInfo != null && txInfo.hasTransaction()) {
if (logger.isDebugEnabled()) logger.debug("Invoking commit for transaction on " + txInfo.joinpointIdentification());
this.transactionManager.commit(txInfo.getTransactionStatus());
}
}
委託 PlatformTransactionManager.commit(TransactionStatus)。
public final void commit(TransactionStatus status) throws TransactionException {
DefaultTransactionStatus defStatus = (DefaultTransactionStatus) status;
if (defStatus.isCompleted()) throw new IllegalTransactionStateException("Transaction already completed");
if (defStatus.isLocalRollbackOnly()) { processRollback(defStatus); return; }
if (!shouldCommitOnGlobalRollbackOnly() && defStatus.isGlobalRollbackOnly()) {
processRollback(defStatus);
throw new UnexpectedRollbackException("Transaction rolled back: marked rollback-only");
}
processCommit(defStatus);
}
關鍵判斷:
isCompleted():防止重複提交/回滾。isLocalRollbackOnly():代碼顯式調用 setRollbackOnly()。isGlobalRollbackOnly():外部事務管理器(JTA)或嵌套事務標記全局回滾。private void processCommit(DefaultTransactionStatus status) throws TransactionException {
try {
boolean beforeCompletionInvoked = false;
try {
triggerBeforeCommit(status); // 同步回調:beforeCommit
triggerBeforeCompletion(status); // 同步回調:beforeCompletion
beforeCompletionInvoked = true;
if (status.hasSavepoint()) { // 嵌套事務:釋放保存點
status.releaseHeldSavepoint();
} else if (status.isNewTransaction()) { // 真正的新事務:提交
boolean globalRollbackOnly = status.isGlobalRollbackOnly();
doCommit(status); // 子類實現:JDBC con.commit()、Hibernate tx.commit()
}
// 後續:觸發 afterCommit、afterCompletion 同步回調
} catch (Throwable ex) {
// 異常處理:觸發 afterCompletion、拋出異常
}
} finally {
cleanupAfterCompletion(status); // 清理同步資源
}
}
這是 區分「外層事務提交」與「內層事務不提交」的分水嶺:
newTransactionStatus(definition, transaction, true, ...) → isNewTransaction() = true → 執行 doCommit()。handleExistingTransaction() → 返回 newTransactionStatus(definition, transaction, false, ...) → isNewTransaction() = false → 跳過 doCommit(),僅釋放保存點(如有)。private TransactionStatus handleExistingTransaction(TransactionDefinition definition, Object transaction, boolean debugEnabled) {
switch (definition.getPropagationBehavior()) {
case PROPAGATION_NEVER: throw new IllegalTransactionStateException("Existing transaction found");
case PROPAGATION_NOT_SUPPORTED:
Object suspended = suspend(transaction); // 掛起現有事務
return newTransactionStatus(definition, null, false, ..., suspended);
case PROPAGATION_REQUIRES_NEW:
Object suspended = suspend(transaction);
doBegin(transaction, definition); // 開啟新事務
return newTransactionStatus(definition, transaction, true, ..., suspended);
case PROPAGATION_NESTED:
if (!isNestedTransactionAllowed()) throw new NestedTransactionNotSupportedException();
if (useSavepointForNestedTransaction()) {
DefaultTransactionStatus status = newTransactionStatus(definition, transaction, false, false, ...);
status.createAndHoldSavepoint(); // 設置保存點
return status;
} else {
doBegin(transaction, definition); // 真正嵌套事務(JTA)
return newTransactionStatus(definition, transaction, true, ..., null);
}
default: // PROPAGATION_SUPPORTS / REQUIRED
return newTransactionStatus(definition, transaction, false, ..., null); // 共用現有事務
}
}
這裡的 newTransactionStatus(..., false, ...) 直接決定了內層方法 不會觸發真實提交。
protected void completeTransactionAfterThrowing(TransactionInfo txInfo, Throwable ex) {
if (txInfo != null && txInfo.hasTransaction()) {
if (txInfo.transactionAttribute.rollbackOn(ex)) { // 判斷異常是否觸發回滾
try {
this.transactionManager.rollback(txInfo.getTransactionStatus());
} catch (TransactionSystemException ex2) {
logger.error("Rollback failed", ex2);
}
} else {
// 異常不觸發回滾:嘗試提交(如樂觀鎖異常)
try {
this.transactionManager.commit(txInfo.getTransactionStatus());
} catch (TransactionSystemException ex2) {
logger.error("Commit failed", ex2);
}
}
}
}
rollbackOn(ex) 默認對 RuntimeException/Error 返回 true,受檢異常返回 false,可通過 @Transactional(rollbackFor/ noRollbackFor) 自定義。
protected void doCommit(DefaultTransactionStatus status) {
DataSourceTransactionObject txObject = (DataSourceTransactionObject) status.getTransaction();
Connection con = txObject.getConnectionHolder().getConnection();
if (status.isDebug()) logger.debug("Committing JDBC transaction on Connection [" + con + "]");
try {
con.commit(); // 核心:JDBC 原生提交
} catch (SQLException ex) {
throw new TransactionSystemException("Could not commit JDBC transaction", ex);
}
}
protected void doRollback(DefaultTransactionStatus status) {
DataSourceTransactionObject txObject = (DataSourceTransactionObject) status.getTransaction();
Connection con = txObject.getConnectionHolder().getConnection();
if (status.isDebug()) logger.debug("Rolling back JDBC transaction on Connection [" + con + "]");
try {
con.rollback(); // 核心:JDBC 原生回滾
} catch (SQLException ex) {
throw new TransactionSystemException("Could not roll back JDBC transaction", ex);
}
}
private void cleanupAfterCompletion(DefaultTransactionStatus status) {
status.setCompleted(); // 標記完成
if (status.isNewSynchronization()) {
TransactionSynchronizationManager.clear(); // 清理 ThreadLocal 綁定的資源、同步回調
}
if (status.isNewTransaction()) {
doCleanupAfterCompletion(status.getTransaction()); // 子類清理:解綁 ConnectionHolder、關閉 Session
}
}
方法出棧
↓
finally → cleanupTransactionInfo() [恢復 TransactionInfo 棧]
↓
正常返回 → commitTransactionAfterReturning()
↓
txInfo.hasTransaction()?
├─ 否 → 結束
└─ 是 → transactionManager.commit(status)
↓
status.isCompleted()? → 異常
status.isLocalRollbackOnly()? → rollback
status.isGlobalRollbackOnly() && !shouldCommitOnGlobalRollbackOnly()? → rollback + UnexpectedRollbackException
↓
processCommit()
↓
triggerBeforeCommit/BeforeCompletion 同步回調
↓
status.hasSavepoint()? → releaseSavepoint (嵌套事務)
status.isNewTransaction()? → doCommit() (真實提交)
↓
triggerAfterCommit/AfterCompletion 同步回調
↓
cleanupAfterCompletion() [清理同步資源、解綁 Connection/Session]
↓
異常拋出 → completeTransactionAfterThrowing()
↓
txInfo.hasTransaction()?
├─ 否 → 結束
└─ 是 → rollbackOn(ex)?
├─ 是 → rollback()
└─ 否 → commit() (非回滾異常嘗試提交)
isNewTransaction() 決定是否真實提交。TransactionStatus.isNewTransaction() = false,提交邏輯自動跳過 doCommit()。status.createAndHoldSavepoint() / releaseHeldSavepoint() 實現 JDBC 3.0 Savepoint 語義。TransactionSynchronization 允許在提交/回滾前後掛鉤(Hibernate flush、緩存失效、郵件發送等)。rollbackFor。理解這套機制,能讓你精準控制事務邊界,排查「事務未提交」、「意外回滾」、「嵌套事務行為不符預期」等生產疑難雜症。