SpringBoot + MyBatis 事务管理全解析:从 @Transactional 到 JDBC Connection 的旅程
SpringBoot + MyBatis 事务管理全解析:从 @Transactional 到 JDBC Connection 的旅程
- 一、JDBC Connection:事务操作的真正执行者
- 1.1 数据库事务的本质
- 1.2 Spring 与 Connection 的协作流程
- 二、从 @Transactional 到 JDBC Connection 的完整链路
- 2.1 Spring 中 TransactionInterceptor 的核心逻辑
- 2.2 TransactionInterceptor 到 DataSourceTransactionManager 的调用链路
- 2.3 DataSourceTransactionManager 的核心实现
- 2.3.1 doBegin 方法:开启事务并绑定资源
- 2.3.2 doCommit 方法:提交事务
- 2.3.3 doRollback 方法:回滚事务
- 2.4 TransactionSynchronizationManager:线程级事务上下文管理
- 三、MyBatis 与 Spring 事务的协作机制
- 3.1 SqlSessionTemplate:Spring 环境下的 MyBatis 会话
- 3.2 获取 Spring 管理的 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. 从数据源获取ConnectionConnection 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@Nullablepublic Object proceedWithInvocation() throws Throwable {return invocation.proceed();}@Overridepublic Object getTarget() {return invocation.getThis();}@Overridepublic 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. 获取或创建新的Connectionif (!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 {@Overridepublic 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
的事务协作机制,不仅能帮助我们正确使用事务,更能在遇到问题时快速定位和解决。完结撒花(*^▽^)!!!*