SpringBoot + MyBatis 事务管理全解析:从 @Transactional 到 JDBC Connection 的旅程

发布于:2025-06-25 ⋅ 阅读:(19) ⋅ 点赞:(0)

在这里插入图片描述

开篇:当我们使用 @Transactional 时,背后发生了什么?

SpringBoot + MyBatis 的项目中,只需在 Service 方法上添加@Transactional注解,就能轻松实现事务管理。但这个过程中,Spring 如何与 MyBatis 协作事务的提交 / 回滚究竟由谁执行?本文将基于spring-tx 5.3.23spring-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; // 回滚事务

无论上层框架如何封装,最终执行事务提交 / 回滚的永远是 JDBCConnection 对象Spring 的事务管理,本质是对这些底层操作的封装与流程控制。

1.2 Spring 与 Connection 的协作流程

Spring 通过DataSourceTransactionManager管理 Connection 的生命周期,关键流程如下:

  1. 获取连接:从数据源 (DataSource) 获取 Connection
  2. 开启事务:调用connection.setAutoCommit(false)
  3. 执行业务逻辑MyBatis 使用该 Connection 执行 SQL
  4. 提交 / 回滚:根据执行结果调用connection.commit()connection.rollback()
  5. 释放连接:将 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 的核心逻辑

TransactionInterceptorSpring 框架中专门用于拦截带有 @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:线程级事务上下文管理

TransactionSynchronizationManagerSpring 事务管理的核心组件,使用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 会话

SqlSessionTemplateSpringMyBatis 集成的核心组件,它会优先使用 Spring 管理的事务连接:

执行 SQL 的核心方法是通过动态代理实现的。具体来说,所有 SQL 操作都会被代理到SqlSessionInterceptor类的invoke方法中处理。这个方法会获取一个 SqlSession 实例,并调用其对应的 SQL 执行方法(如selectOneinsertupdate等)

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;
  }

四、完整链路总结:从注解到数据库的七步旅程

  1. 注解解析
    Spring 通过@Transactional注解获取事务属性配置
  2. AOP 拦截
    TransactionInterceptor拦截目标方法调用
  3. 事务管理器获取
    根据配置获取DataSourceTransactionManager实例
  4. 开启事务
    调用doBegin()
    • 从数据源获取 Connection
    • 设置autoCommit=false
    • Connection 绑定到TransactionSynchronizationManager
  5. 执行 SQL
    MyBatis 通过SqlSessionTemplate获取 Spring 管理的 Connection 执行 SQL
  6. 提交 / 回滚事务
    根据执行结果调用doCommit()doRollback(),最终调用 Connection 的对应方法
  7. 资源清理
    释放 Connection,解除与当前线程的绑定

理解 SpringMyBatis 的事务协作机制,不仅能帮助我们正确使用事务,更能在遇到问题时快速定位和解决。完结撒花(*^▽^)!!!*


网站公告

今日签到

点亮在社区的每一天
去签到