在Spring框架中,声明式事务管理(通过@Transactional注解)极大地简化了企业应用的事务处理。只需简单的一行注解,Spring便能自动为我们开启、提交或回滚事务。然而,这背后究竟隐藏着怎样的机制?当业务方法抛出异常时,Spring如何确保数据库连接执行ROLLBACK?本文将基于Spring事务模块的核心源码,逐步剖析从拦截器到数据库驱动的完整回滚调用链,带您一探究竟。
一、起点:事务拦截器TransactionInterceptor
Spring声明式事务基于AOP实现,其核心增强逻辑封装在TransactionInterceptor中。该类实现了MethodInterceptor接口,在目标方法执行前后织入事务管理逻辑。
java
less
@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);
}
invoke方法首先确定目标类(可能为接口),然后调用invokeWithinTransaction方法,并传入当前方法、目标类以及一个回调invocation::proceed(用于执行原始方法)。这里采用了模板方法模式,将事务处理的核心逻辑交由子类或辅助方法完成。
二、核心处理:invokeWithinTransaction
invokeWithinTransaction是事务处理的真正核心,它负责解析事务属性、选择事务管理器,并根据不同情况执行响应式事务或平台事务。为聚焦回滚场景,我们重点关注平台事务管理器 (PlatformTransactionManager)分支。
java
java
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);
final TransactionManager tm = determineTransactionManager(txAttr);
// ... 响应式事务分支略 ...
PlatformTransactionManager ptm = asPlatformTransactionManager(tm);
final String joinpointIdentification = methodIdentification(method, targetClass, txAttr);
if (txAttr == null || !(ptm instanceof CallbackPreferringPlatformTransactionManager)) {
// 标准事务处理
TransactionInfo txInfo = createTransactionIfNecessary(ptm, txAttr, joinpointIdentification);
Object retVal;
try {
retVal = invocation.proceedWithInvocation(); // 执行目标方法
} catch (Throwable ex) {
completeTransactionAfterThrowing(txInfo, ex); // 异常时完成事务
throw ex;
} finally {
cleanupTransactionInfo(txInfo);
}
commitTransactionAfterReturning(txInfo); // 正常返回时提交
return retVal;
} else {
// CallbackPreferring 分支(如使用编程式事务回调)
// ... 略 ...
}
}
该方法的流程清晰:
- 获取事务属性(
TransactionAttribute),若为null则表示方法非事务性。 - 确定事务管理器(
PlatformTransactionManager)。 - 根据事务管理器类型选择处理分支:标准事务或回调优先事务。
- 在标准事务分支中,先通过
createTransactionIfNecessary开启事务(获取或创建TransactionInfo),然后执行目标方法,最后根据执行结果决定提交或回滚。
当目标方法抛出异常时,completeTransactionAfterThrowing将接管处理。
三、异常处理:completeTransactionAfterThrowing
java
typescript
protected void completeTransactionAfterThrowing(@Nullable TransactionInfo txInfo, Throwable ex) {
if (txInfo != null && txInfo.getTransactionStatus() != null) {
if (txInfo.transactionAttribute != null && txInfo.transactionAttribute.rollbackOn(ex)) {
try {
txInfo.getTransactionManager().rollback(txInfo.getTransactionStatus());
} catch (TransactionSystemException | RuntimeException | Error ex2) {
// 异常覆盖处理
throw ex2;
}
} else {
try {
txInfo.getTransactionManager().commit(txInfo.getTransactionStatus());
} catch (Exception ex2) {
// 异常覆盖处理
}
}
}
}
此方法根据事务属性中定义的回滚规则 (rollbackOn(ex))决定是回滚还是提交。默认情况下,Spring仅对RuntimeException和Error进行回滚,检查型异常不会触发回滚。若需自定义回滚异常,可通过@Transactional(rollbackFor = ...)指定。
若判定需要回滚,则调用事务管理器的rollback方法;否则执行commit。接下来,我们以DataSourceTransactionManager为例,深入回滚的内部实现。
四、事务管理器回滚:DataSourceTransactionManager.doRollback
DataSourceTransactionManager是Spring JDBC模块提供的事务管理器,它基于底层数据库连接实现事务控制。其rollback方法最终委托给doRollback:
java
java
@Override
protected void doRollback(DefaultTransactionStatus status) {
DataSourceTransactionObject txObject = (DataSourceTransactionObject) status.getTransaction();
Connection con = txObject.getConnectionHolder().getConnection();
try {
con.rollback(); // 调用数据库连接的rollback()
} catch (SQLException ex) {
throw new TransactionSystemException("Could not roll back JDBC transaction", ex);
}
}
这里从事务对象中获取当前持有的数据库连接,直接调用java.sql.Connection#rollback()。注意,此连接可能被连接池包装,实际调用的是连接池代理类的rollback方法。
五、连接池与驱动层的最终落地
现代应用大多使用数据库连接池(如Druid、HikariCP)来管理连接。当Spring调用con.rollback()时,实际上调用的是连接池代理对象的rollback方法,最终会路由到底层数据库驱动的真实连接。
以Druid连接池 为例,其DruidPooledConnection的rollback方法:
java
java
@Override
public void rollback() throws SQLException {
if (transactionInfo == null) { return; }
if (holder == null) { return; }
DruidAbstractDataSource dataSource = holder.getDataSource();
dataSource.incrementRollbackCount(); // 统计回滚次数
try {
conn.rollback(); // conn为物理连接(可能是驱动实现)
} catch (SQLException ex) {
handleException(ex, null);
} finally {
handleEndTransaction(dataSource, null);
}
}
Druik通过Filter链进一步扩展,但最终都会调用物理连接的rollback。DruidConnectionWrapper的rollback会经过Filter链,最后回到原生连接的rollback。
再看MySQL Connector/J的实现:
java
java
@Override
public void rollback() throws SQLException {
synchronized (getConnectionMutex()) {
checkClosed();
// 执行拦截器、检查自动提交等
try {
rollbackNoChecks(); // 无检查的回滚
} catch (SQLException sqlEx) {
// 处理异常
}
}
}
private void rollbackNoChecks() throws SQLException {
synchronized (getConnectionMutex()) {
if (this.useLocalTransactionState.getValue()) {
if (!this.session.getServerSession().inTransactionOnServer()) {
return; // 无活动事务,直接返回
}
}
this.session.execSQL(null, "rollback", -1, null, false, ...); // 发送"ROLLBACK"语句
}
}
最终,MySQL驱动通过execSQL向数据库发送ROLLBACK命令,由数据库完成事务的回滚。至此,从@Transactional到物理数据库的完整调用链浮出水面。
六、总结:清晰分层,灵活扩展
通过上述源码追踪,我们可以看到Spring事务回滚的清晰层次:
- AOP拦截层 :
TransactionInterceptor拦截方法调用,将事务管理委托给invokeWithinTransaction。 - 事务抽象层 :
TransactionAspectSupport提供统一的事务处理模板,包括事务创建、提交、回滚。 - 事务管理器层 :
PlatformTransactionManager的具体实现(如DataSourceTransactionManager)管理底层资源(如数据库连接)的事务操作。 - 连接池层:如Druid,对物理连接进行包装,提供统计、拦截等功能,最终调用驱动的原生方法。
- 数据库驱动层 :发送
ROLLBACK命令至数据库,完成最终的事务回滚。
这种分层设计使Spring事务管理具有高度的灵活性和可扩展性------开发者可以轻松替换事务管理器、连接池,甚至集成其他事务资源(如JTA、Hibernate)。理解这一底层原理,不仅有助于我们诊断事务相关的问题,还能在需要时进行定制化扩展。
希望本文能帮助您揭开Spring事务回滚的神秘面纱。在未来的开发中,当您再次使用@Transactional时,或许会对背后的这趟"旅程"多一分敬意。
源码
@Override
@Nullable
public Object invoke(MethodInvocation invocation) throws Throwable {
// Work out the target class: may be {@code null}.
// The TransactionAttributeSource should be passed the target class
// as well as the method, which may be from an interface.
Class<?> targetClass = (invocation.getThis() != null ? AopUtils.getTargetClass(invocation.getThis()) : null);
// Adapt to TransactionAspectSupport's invokeWithinTransaction...
return invokeWithinTransaction(invocation.getMethod(), targetClass, invocation::proceed);
}
@Nullable
protected Object invokeWithinTransaction(Method method, @Nullable Class<?> targetClass,
final InvocationCallback invocation) throws Throwable {
// If the transaction attribute is null, the method is non-transactional.
TransactionAttributeSource tas = getTransactionAttributeSource();
final TransactionAttribute txAttr = (tas != null ? tas.getTransactionAttribute(method, targetClass) : null);
final TransactionManager tm = determineTransactionManager(txAttr);
if (this.reactiveAdapterRegistry != null && tm instanceof ReactiveTransactionManager) {
ReactiveTransactionSupport txSupport = this.transactionSupportCache.computeIfAbsent(method, key -> {
if (KotlinDetector.isKotlinType(method.getDeclaringClass()) && KotlinDelegate.isSuspend(method)) {
throw new TransactionUsageException(
"Unsupported annotated transaction on suspending function detected: " + method +
". Use TransactionalOperator.transactional extensions instead.");
}
ReactiveAdapter adapter = this.reactiveAdapterRegistry.getAdapter(method.getReturnType());
if (adapter == null) {
throw new IllegalStateException("Cannot apply reactive transaction to non-reactive return type: " +
method.getReturnType());
}
return new ReactiveTransactionSupport(adapter);
});
return txSupport.invokeWithinTransaction(
method, targetClass, invocation, txAttr, (ReactiveTransactionManager) tm);
}
PlatformTransactionManager ptm = asPlatformTransactionManager(tm);
final String joinpointIdentification = methodIdentification(method, targetClass, txAttr);
if (txAttr == null || !(ptm instanceof CallbackPreferringPlatformTransactionManager)) {
// Standard transaction demarcation with getTransaction and commit/rollback calls.
TransactionInfo txInfo = createTransactionIfNecessary(ptm, txAttr, joinpointIdentification);
Object retVal;
try {
// This is an around advice: Invoke the next interceptor in the chain.
// This will normally result in a target object being invoked.
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();
// It's a CallbackPreferringPlatformTransactionManager: pass a TransactionCallback in.
try {
result = ((CallbackPreferringPlatformTransactionManager) ptm).execute(txAttr, status -> {
TransactionInfo txInfo = prepareTransactionInfo(ptm, txAttr, joinpointIdentification, status);
try {
Object retVal = invocation.proceedWithInvocation();
if (retVal != null && vavrPresent && VavrDelegate.isVavrTry(retVal)) {
// Set rollback-only in case of Vavr failure matching our rollback rules...
retVal = VavrDelegate.evaluateTryFailure(retVal, txAttr, status);
}
return retVal;
}
catch (Throwable ex) {
if (txAttr.rollbackOn(ex)) {
// A RuntimeException: will lead to a rollback.
if (ex instanceof RuntimeException) {
throw (RuntimeException) ex;
}
else {
throw new ThrowableHolderException(ex);
}
}
else {
// A normal return value: will lead to a commit.
throwableHolder.throwable = ex;
return null;
}
}
finally {
cleanupTransactionInfo(txInfo);
}
});
}
catch (ThrowableHolderException ex) {
throw ex.getCause();
}
catch (TransactionSystemException ex2) {
if (throwableHolder.throwable != null) {
logger.error("Application exception overridden by commit exception", throwableHolder.throwable);
ex2.initApplicationException(throwableHolder.throwable);
}
throw ex2;
}
catch (Throwable ex2) {
if (throwableHolder.throwable != null) {
logger.error("Application exception overridden by commit exception", throwableHolder.throwable);
}
throw ex2;
}
// Check result state: It might indicate a Throwable to rethrow.
if (throwableHolder.throwable != null) {
throw throwableHolder.throwable;
}
return result;
}
}
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);
}
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 {
// We don't roll back on this exception.
// Will still roll back if TransactionStatus.isRollbackOnly() is true.
try {
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;
}
}
}
}
@Override
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, false);
}
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 {
// Participating in larger transaction
if (status.hasTransaction()) {
if (status.isLocalRollbackOnly() || isGlobalRollbackOnParticipationFailure()) {
if (status.isDebug()) {
logger.debug("Participating transaction failed - marking existing transaction as rollback-only");
}
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);
// Raise UnexpectedRollbackException if we had a global rollback-only marker
if (unexpectedRollback) {
throw new UnexpectedRollbackException(
"Transaction rolled back because it has been marked as rollback-only");
}
}
finally {
cleanupAfterCompletion(status);
}
}
@Override
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();
}
catch (SQLException ex) {
throw new TransactionSystemException("Could not roll back JDBC transaction", ex);
}
}
@Override
public void rollback() throws SQLException {
if (transactionInfo == null) {
return;
}
if (holder == null) {
return;
}
DruidAbstractDataSource dataSource = holder.getDataSource();
dataSource.incrementRollbackCount();
try {
conn.rollback();
} catch (SQLException ex) {
handleException(ex, null);
} finally {
handleEndTransaction(dataSource, null);
}
}
@Override
public void rollback() throws SQLException {
FilterChainImpl chain = createChain();
chain.connection_rollback(this);
recycleFilterChain(chain);
if (transactionInfo != null) {
transactionInfo.setEndTimeMillis();
}
}
@Override
public void connection_rollback(ConnectionProxy connection) throws SQLException {
if (this.pos < filterSize) {
nextFilter()
.connection_rollback(this, connection);
return;
}
connection.getRawObject()
.rollback();
}
@Override
public void connection_rollback(FilterChain chain, ConnectionProxy connection) throws SQLException {
chain.connection_rollback(connection);
}
@Override
public void rollback() throws SQLException {
synchronized (getConnectionMutex()) {
checkClosed();
try {
if (this.connectionLifecycleInterceptors != null) {
IterateBlock<ConnectionLifecycleInterceptor> iter = new IterateBlock<ConnectionLifecycleInterceptor>(
this.connectionLifecycleInterceptors.iterator()) {
@Override
void forEach(ConnectionLifecycleInterceptor each) throws SQLException {
if (!each.rollback()) {
this.stopIterating = true;
}
}
};
iter.doForAll();
if (!iter.fullIteration()) {
return;
}
}
if (this.session.getServerSession().isAutoCommit()) {
throw SQLError.createSQLException(Messages.getString("Connection.20"), MysqlErrorNumbers.SQL_STATE_CONNECTION_NOT_OPEN,
getExceptionInterceptor());
}
try {
rollbackNoChecks();
} catch (SQLException sqlEx) {
// We ignore non-transactional tables if told to do so
if (this.ignoreNonTxTables.getInitialValue() && (sqlEx.getErrorCode() == MysqlErrorNumbers.ER_WARNING_NOT_COMPLETE_ROLLBACK)) {
return;
}
throw sqlEx;
}
} catch (SQLException sqlException) {
if (MysqlErrorNumbers.SQL_STATE_COMMUNICATION_LINK_FAILURE.equals(sqlException.getSQLState())) {
throw SQLError.createSQLException(Messages.getString("Connection.21"), MysqlErrorNumbers.SQL_STATE_TRANSACTION_RESOLUTION_UNKNOWN,
getExceptionInterceptor());
}
throw sqlException;
} finally {
this.session.setNeedsPing(this.reconnectAtTxEnd.getValue());
}
}
}
private void rollbackNoChecks() throws SQLException {
synchronized (getConnectionMutex()) {
if (this.useLocalTransactionState.getValue()) {
if (!this.session.getServerSession().inTransactionOnServer()) {
return; // effectively a no-op
}
}
this.session.execSQL(null, "rollback", -1, null, false, this.nullStatementResultSetFactory, null, false);
}
}