Spring事务回滚探秘:从@Transactional到数据库连接的完整旅程

在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 分支(如使用编程式事务回调)
        // ... 略 ...
    }
}

该方法的流程清晰:

  1. 获取事务属性(TransactionAttribute),若为null则表示方法非事务性。
  2. 确定事务管理器(PlatformTransactionManager)。
  3. 根据事务管理器类型选择处理分支:标准事务或回调优先事务。
  4. 在标准事务分支中,先通过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仅对RuntimeExceptionError进行回滚,检查型异常不会触发回滚。若需自定义回滚异常,可通过@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连接池 为例,其DruidPooledConnectionrollback方法:

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链进一步扩展,但最终都会调用物理连接的rollbackDruidConnectionWrapperrollback会经过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事务回滚的清晰层次:

  1. AOP拦截层TransactionInterceptor拦截方法调用,将事务管理委托给invokeWithinTransaction
  2. 事务抽象层TransactionAspectSupport提供统一的事务处理模板,包括事务创建、提交、回滚。
  3. 事务管理器层PlatformTransactionManager的具体实现(如DataSourceTransactionManager)管理底层资源(如数据库连接)的事务操作。
  4. 连接池层:如Druid,对物理连接进行包装,提供统计、拦截等功能,最终调用驱动的原生方法。
  5. 数据库驱动层 :发送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);

        }
    }
		
相关推荐
焦糖玛奇朵婷1 小时前
盲盒小程序一站式开发
java·大数据·服务器·前端·小程序
yatum_20141 小时前
VirtualBox 搭建 Hadoop-2.7.3 集群完整安装总结
java·ide·eclipse
番茄去哪了1 小时前
高并发选课系统页面阻塞现象的技术原理分析:同步交互与悲观锁机制
java·缓存·科普·面向对象编程
雨夜之寂1 小时前
能动手才推 · AI · 03/14
后端
程序员Terry1 小时前
别老写重复代码了!模版方法模式一次讲透
java·设计模式
是2的10次方啊1 小时前
String.format 替换踩坑记:从遇坑、读源码到手写实现
java·源码阅读
Cache技术分享2 小时前
351. Java IO API - Java 文件操作:java.io.File 与 java.nio.file 功能对比 - 3
前端·后端
QZQ541882 小时前
redis分布式锁相关思考
后端
不光头强2 小时前
手写tomcat
java·tomcat