SpringBoot + MyBatis 事务管理全解析:从 @Transactional 到 JDBC Connection 的旅程
开篇:当我们使用 @Transactional
时,背后发生了什么?
在 SpringBoot + MyBatis
的项目中,只需在 Service
方法上添加@Transactional
注解,就能轻松实现事务管理。但这个过程中,Spring
如何与 MyBatis
协作?事务的提交 / 回滚究竟由谁执行?本文将基于spring-tx 5.3.23
和spring-boot-starter 2.2.2
版本,深入剖析从注解到数据库的完整链路。
一、JDBC Connection:事务操作的真正执行者
1.1 数据库事务的本质
在 JDBC
规范中,所有事务操作都由Connection
接口定义:
// java.sql.Connection接口核心方法
void setAutoCommit(boolean autoCommit) throws SQLException; // 开启/关闭自动提交
void commit() throws SQLException; // 提交事务
void rollback() throws SQLException; // 回滚事务
无论上层框架如何封装,最终执行事务提交 / 回滚的永远是 JDBC
的 Connection
对象。Spring
的事务管理,本质是对这些底层操作的封装与流程控制。
1.2 Spring 与 Connection 的协作流程
Spring
通过DataSourceTransactionManager
管理 Connection
的生命周期,关键流程如下:
- 获取连接:从数据源 (
DataSource
) 获取Connection
- 开启事务:调用
connection.setAutoCommit(false)
- 执行业务逻辑:
MyBatis
使用该Connection
执行SQL
- 提交 / 回滚:根据执行结果调用
connection.commit()
或connection.rollback()
- 释放连接:将
Connection
返回给连接池
伪代码展示Spring
管理Connection
的核心逻辑:
// 伪代码展示Spring管理Connection的核心逻辑
try {
// 1. 从数据源获取Connection
Connection conn = dataSource.getConnection();
// 2. 关闭自动提交,开启事务
conn.setAutoCommit(false);
try {
// 3. 执行SQL操作(MyBatis使用此Connection)
userMapper.insert(user);
orderMapper.createOrder(order);
// 4. 提交事务
conn.commit();
} catch (Exception e) {
// 5. 异常时回滚事务
conn.rollback();
} finally {
// 6. 释放连接
conn.close(); // 实际由连接池管理
}
} catch (SQLException ex) {
throw new RuntimeException("数据库操作失败", ex);
}
二、从 @Transactional 到 JDBC Connection 的完整链路
2.1 Spring 中 TransactionInterceptor 的核心逻辑
TransactionInterceptor
是 Spring
框架中专门用于拦截带有 @Transactional
注解方法的 AOP 拦截器
TransactionInterceptor
类继承自 TransactionAspectSupport
,并实现了 MethodInterceptor
接口。在 Spring
的事务自动代理机制中,@Transactional
注解会被 TransactionAttributeSource
解析,最终触发 TransactionInterceptor
的拦截逻辑
在 Spring 5.3.23
版本中,TransactionInterceptor
的核心逻辑如下:
/**
* AOP 方法拦截器的核心实现,用于在事务环境中执行目标方法
*/
@Override
@Nullable
public Object invoke(MethodInvocation invocation) throws Throwable {
// TransactionAttributeSource 需要同时传入目标类和方法(方法可能来自接口)
Class<?> targetClass = (invocation.getThis() != null ? AopUtils.getTargetClass(invocation.getThis()) : null);
// 委托给 TransactionAspectSupport 的核心事务处理方法。传入目标方法、目标类和自定义的调用回调
return invokeWithinTransaction(invocation.getMethod(), targetClass, new CoroutinesInvocationCallback() {
/**
* 继续执行拦截链,最终会调用目标方法
*/
@Override
@Nullable
public Object proceedWithInvocation() throws Throwable {
return invocation.proceed();
}
@Override
public Object getTarget() {
return invocation.getThis();
}
@Override
public Object[] getArguments() {
return invocation.getArguments();
}
});
}
invoke
方法:
- 作为
AOP
拦截器的入口,负责拦截方法调用 - 解析目标类和方法信息
- 创建回调接口,连接事务管理器和目标方法
invokeWithinTransaction
方法:
- 事务管理的核心实现
- 根据事务属性配置创建事务
- 执行目标方法并处理返回值
- 根据执行结果决定提交或回滚事务
2.2 TransactionInterceptor 到 DataSourceTransactionManager 的调用链路
整个调用链路可分为以下关键步骤:
// 关键调用链路伪代码
TransactionInterceptor.invoke()
→ TransactionAspectSupport.invokeWithinTransaction()
→ createTransactionIfNecessary() // 创建事务
→ AbstractPlatformTransactionManager.getTransaction()
→ DataSourceTransactionManager.doBegin() // 开启事务
→ invocation.proceedWithInvocation(); // 执行目标方法(包含MyBatis SQL)
→ commitTransactionAfterReturning() // 正常返回后提交
→ AbstractPlatformTransactionManager.commit()
→ DataSourceTransactionManager.doCommit()
→ completeTransactionAfterThrowing() // 异常时回滚
→ AbstractPlatformTransactionManager.rollback()
→ DataSourceTransactionManager.doRollback()
2.3 DataSourceTransactionManager 的核心实现
2.3.1 doBegin 方法:开启事务并绑定资源
@Override
protected void doBegin(Object transaction, TransactionDefinition definition) {
DataSourceTransactionObject txObject = (DataSourceTransactionObject) transaction;
Connection con = null;
try {
// 1. 获取或创建新的Connection
if (!txObject.hasConnectionHolder() ||
txObject.getConnectionHolder().isSynchronizedWithTransaction()) {
Connection newCon = obtainDataSource().getConnection();
if (logger.isDebugEnabled()) {
logger.debug("Acquired Connection [" + newCon + "] for JDBC transaction");
}
txObject.setConnectionHolder(new ConnectionHolder(newCon), true);
}
// 2. 准备Connection用于事务
txObject.getConnectionHolder().setSynchronizedWithTransaction(true);
con = txObject.getConnectionHolder().getConnection();
// 3. 设置隔离级别
Integer previousIsolationLevel = DataSourceUtils.prepareConnectionForTransaction(con, definition);
txObject.setPreviousIsolationLevel(previousIsolationLevel);
txObject.setReadOnly(definition.isReadOnly());
// 4. 【关键】:关闭自动提交,开启事务
if (con.getAutoCommit()) {
txObject.setMustRestoreAutoCommit(true);
if (logger.isDebugEnabled()) {
logger.debug("Switching JDBC Connection [" + con + "] to manual commit");
}
con.setAutoCommit(false);
}
// 5. 准备事务同步
prepareTransactionalConnection(con, definition);
txObject.getConnectionHolder().setTransactionActive(true);
// 6. 超时设置
int timeout = determineTimeout(definition);
if (timeout != TransactionDefinition.TIMEOUT_DEFAULT) {
txObject.getConnectionHolder().setTimeoutInSeconds(timeout);
}
// 7. 【关键】:将ConnectionHolder绑定到当前线程
if (txObject.isNewConnectionHolder()) {
TransactionSynchronizationManager.bindResource(obtainDataSource(), txObject.getConnectionHolder());
}
}
catch (Throwable ex) {
// 异常处理...
}
}
关键步骤解析:
- 步骤 4:调用
con.setAutoCommit(false)
开启事务模式 - 步骤 7:通过
TransactionSynchronizationManager.bindResource()
将Connection
绑定到当前线程
2.3.2 doCommit 方法:提交事务
protected void doCommit(DefaultTransactionStatus status) {
DataSourceTransactionObject txObject = (DataSourceTransactionObject) status.getTransaction();
Connection con = txObject.getConnectionHolder().getConnection();
try {
// 核心:调用JDBC Connection的commit方法
con.commit();
}
catch (SQLException ex) {
throw new TransactionSystemException("Could not commit JDBC transaction", ex);
}
}
2.3.3 doRollback 方法:回滚事务
protected void doRollback(DefaultTransactionStatus status) {
DataSourceTransactionObject txObject = (DataSourceTransactionObject) status.getTransaction();
Connection con = txObject.getConnectionHolder().getConnection();
try {
// 核心:调用JDBC Connection的rollback方法
con.rollback();
}
catch (SQLException ex) {
throw new TransactionSystemException("Could not roll back JDBC transaction", ex);
}
}
2.4 TransactionSynchronizationManager:线程级事务上下文管理
TransactionSynchronizationManager
是 Spring
事务管理的核心组件,使用ThreadLocal
存储当前线程的事务资源:
// org.springframework.transaction.support.TransactionSynchronizationManager (Spring 5.3.23)
private static final ThreadLocal<Map<Object, Object>> resources =
new NamedThreadLocal<>("Transactional resources");
private static final ThreadLocal<Set<TransactionSynchronization>> synchronizations =
new NamedThreadLocal<>("Transaction synchronizations");
private static final ThreadLocal<String> currentTransactionName =
new NamedThreadLocal<>("Current transaction name");
// 绑定资源到当前线程
public static void bindResource(Object key, Object value) throws IllegalStateException {
Map<Object, Object> map = resources.get();
if (map == null) {
map = new HashMap<>();
resources.set(map);
}
Object oldValue = map.put(key, value);
if (oldValue != null) {
throw new IllegalStateException("Already value for key [" + key + "]");
}
}
// 从当前线程获取资源
public static Object getResource(Object key) {
Map<Object, Object> map = resources.get();
return (map != null ? map.get(key) : null);
}
关键绑定点:
在DataSourceTransactionManager.doBegin()
方法中,通过以下代码将 ConnectionHolder
绑定到当前线程:
TransactionSynchronizationManager.bindResource(obtainDataSource(), txObject.getConnectionHolder());
三、MyBatis 与 Spring 事务的协作机制
3.1 SqlSessionTemplate:Spring 环境下的 MyBatis 会话
SqlSessionTemplate
是 Spring
与 MyBatis
集成的核心组件,它会优先使用 Spring
管理的事务连接:
执行 SQL
的核心方法是通过动态代理实现的。具体来说,所有 SQL
操作都会被代理到SqlSessionInterceptor
类的invoke
方法中处理。这个方法会获取一个 SqlSession
实例,并调用其对应的 SQL
执行方法(如selectOne
、insert
、update
等)
private class SqlSessionInterceptor implements InvocationHandler {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 1. 从Spring事务上下文中获取SqlSession(或创建新的)
SqlSession sqlSession = getSqlSession(
SqlSessionTemplate.this.sqlSessionFactory,
SqlSessionTemplate.this.executorType,
SqlSessionTemplate.this.exceptionTranslator);
try {
// 2. 通过反射调用SqlSession的实际方法(如selectOne、insert等)
Object result = method.invoke(sqlSession, args);
// 3. 如果不是事务管理的SqlSession,则手动提交
if (!isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {
sqlSession.commit(true);
}
return result;
} catch (Throwable t) {
// 异常处理...
} finally {
// 4. 关闭SqlSession(如果不是事务管理的)
if (sqlSession != null) {
closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
}
}
}
}
3.2 获取 Spring 管理的 Connection
getSqlSession()
方法最终会调用SqlSessionUtils
工具类,尝试从TransactionSynchronizationManager
获取当前事务上下文中的 SqlSession
:
/**
* 获取MyBatis的SqlSession实例,支持事务同步管理
*/
public static SqlSession getSqlSession(SqlSessionFactory sessionFactory, ExecutorType executorType,
PersistenceExceptionTranslator exceptionTranslator) {
// 参数校验...
// 从当前事务同步管理器中获取已绑定的SqlSession资源
// 【核心逻辑】:事务中的SqlSession会绑定到当前线程
SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory);
// 从现有持有者中获取SqlSession(优先使用已存在的会话)
SqlSession session = sessionHolder(executorType, holder);
if (session != null) {
return session;
}
// 调用MyBatis工厂方法创建新会话(指定执行器类型)
session = sessionFactory.openSession(executorType);
// 注册SqlSession到事务同步管理器(关键逻辑:实现事务内会话共享)
registerSessionHolder(sessionFactory, executorType, exceptionTranslator, session);
return session;
}
四、完整链路总结:从注解到数据库的七步旅程
- 注解解析
Spring
通过@Transactional
注解获取事务属性配置 - AOP 拦截
TransactionInterceptor
拦截目标方法调用 - 事务管理器获取
根据配置获取DataSourceTransactionManager
实例 - 开启事务
调用doBegin()
:- 从数据源获取
Connection
- 设置
autoCommit=false
- 将
Connection
绑定到TransactionSynchronizationManager
- 从数据源获取
- 执行 SQL
MyBatis
通过SqlSessionTemplate
获取Spring
管理的Connection
执行SQL
- 提交 / 回滚事务
根据执行结果调用doCommit()
或doRollback()
,最终调用Connection
的对应方法 - 资源清理
释放Connection
,解除与当前线程的绑定
理解 Spring
与 MyBatis
的事务协作机制,不仅能帮助我们正确使用事务,更能在遇到问题时快速定位和解决。完结撒花(*^▽^)!!!*