乐于分享
好东西不私藏

MyBatis 源码深度拆解(十一):MyBatis 事务管理 & 与 Spring 整合原理

MyBatis 源码深度拆解(十一):MyBatis 事务管理 & 与 Spring 整合原理

面试官:MyBatis 的事务是如何管理的?和 Spring 整合后,为什么不再需要手动 commit/rollback?Spring 是如何接管 MyBatis 事务的?

一、开篇

事务管理是持久层框架的核心能力之一。MyBatis 本身提供了轻量级的事务管理机制,但在与 Spring 整合后,事务控制权被透明地转移给了 Spring 的声明式事务管理。

本篇目标

  • 深入 MyBatis 原生事务管理体系(Transaction接口及实现)
  • 理解JdbcTransactionManagedTransaction的区别
  • 追踪 Spring 整合后事务的接管机制(SqlSessionTemplate+ 事务同步器)
  • 解析@Transactional下为什么不需要手动 commit/rollback
  • 搞清楚 Spring 与 MyBatis 事务同步的底层原理

二、MyBatis 原生事务管理体系

2.1 Transaction 接口

// org.apache.ibatis.transaction.Transactionpublic interface Transaction {    Connection getConnection() throws SQLException;    voidcommit() throws SQLException;    voidrollback() throws SQLException;    voidclose() throws SQLException;    Integer getTimeout() throws SQLException;}

三、JdbcTransaction — 原生 JDBC 事务

JdbcTransaction 是 MyBatis 默认的事务实现,直接使用 JDBC 的 Connection 进行事务控制。

public class JdbcTransaction implements Transaction {    private static final Log log = LogFactory.getLog(JdbcTransaction.class);    protected Connection connection;    protected DataSource dataSource;    protected TransactionIsolationLevel level;    protected boolean autoCommit;    protected boolean skipSetAutoCommitOnClose;  // ⭐ 控制关闭时是否重置 autoCommit    // 构造函数1:从 DataSource 创建    public JdbcTransaction(DataSource ds, TransactionIsolationLevel desiredLevel, boolean desiredAutoCommit) {        this(ds, desiredLevel, desiredAutoCommit, false);    }    // 构造函数2:从 DataSource 创建,可指定 skipSetAutoCommitOnClose    public JdbcTransaction(DataSource ds, TransactionIsolationLevel desiredLevel,                            boolean desiredAutoCommit, boolean skipSetAutoCommitOnClose) {        dataSource = ds;        level = desiredLevel;        autoCommit = desiredAutoCommit;        this.skipSetAutoCommitOnClose = skipSetAutoCommitOnClose;    }    // 构造函数3:从已有 Connection 创建(如 Spring 整合时传入已绑定的连接)    public JdbcTransaction(Connection connection) {        this.connection = connection;    }    @Override    public Connection getConnection() throws SQLException {        if (connection == null) {            openConnection();   // 延迟获取连接        }        return connection;    }    @Override    public void commit() throws SQLException {        // 仅当连接存在且不是自动提交模式时才提交        if (connection != null && !connection.getAutoCommit()) {            if (log.isDebugEnabled()) {                log.debug("Committing JDBC Connection [" + connection + "]");            }            connection.commit();        }    }    @Override    public void rollback() throws SQLException {        // 仅当连接存在且不是自动提交模式时才回滚        if (connection != null && !connection.getAutoCommit()) {            if (log.isDebugEnabled()) {                log.debug("Rolling back JDBC Connection [" + connection + "]");            }            connection.rollback();        }    }    @Override    public void close() throws SQLException {        if (connection != null) {            // ⭐ 关键:关闭前重置 autoCommit            resetAutoCommit();            if (log.isDebugEnabled()) {                log.debug("Closing JDBC Connection [" + connection + "]");            }            connection.close();        }    }    protected void openConnection() throws SQLException {        if (log.isDebugEnabled()) {            log.debug("Opening JDBC Connection");        }        connection = dataSource.getConnection();        if (level != null) {            connection.setTransactionIsolation(level.getLevel());        }        // 通过独立方法设置 autoCommit(会检查当前值)        setDesiredAutoCommit(autoCommit);    }    // 设置 autoCommit,先检查当前值是否已经是期望值    protected void setDesiredAutoCommit(boolean desiredAutoCommit) {        try {            if (connection.getAutoCommit() != desiredAutoCommit) {                if (log.isDebugEnabled()) {                    log.debug("Setting autocommit to " + desiredAutoCommit + " on JDBC Connection [" + connection + "]");                }                connection.setAutoCommit(desiredAutoCommit);            }        } catch (SQLException e) {            throw new TransactionException(                "Error configuring AutoCommit.  Your driver may not support getAutoCommit() or setAutoCommit(). "                + "Requested setting: " + desiredAutoCommit + ".  Cause: " + e, e);        }    }    // 关闭连接前重置 autoCommit 为 true    protected void resetAutoCommit() {        try {            if (!skipSetAutoCommitOnClose && !connection.getAutoCommit()) {                // MyBatis 不会在没有更新操作时调用 commit/rollback。                // 某些数据库(如 Sybase)在关闭连接前要求必须提交或回滚,                // 将 autoCommit 设为 true 可以避免这个问题。                if (log.isDebugEnabled()) {                    log.debug("Resetting autocommit to true on JDBC Connection [" + connection + "]");                }                connection.setAutoCommit(true);            }        } catch (SQLException e) {            if (log.isDebugEnabled()) {                log.debug("Error resetting autocommit to true before closing the connection.  Cause: " + e);            }        }    }    @Override    public Integer getTimeout() throws SQLException {        return null;    }}

3.1 关键机制解读

(1)resetAutoCommit()— 关闭前的兼容性处理

某些数据库驱动(如 Sybase、一些老版本 Oracle 驱动)在关闭Connection时,如果当前处于非自动提交模式且事务未提交/回滚,会抛出异常。resetAutoCommit()在关闭连接前将autoCommit重置为true,避免了这个问题。

(2)commit()/rollback()的条件判断

如果连接已经是自动提交模式(autoCommit=true),每条 SQL 执行后都已自动提交,此时commit()不应再做任何操作。这个判断确保 MyBatis 不会在自动提交模式下错误地提交事务。

(3)skipSetAutoCommitOnClose字段

某些场景下(如 Spring 整合),连接关闭由 Spring 管理,MyBatis 不需要干预autoCommit,此时可设为true跳过重置。

(4)三个构造函数

  • 两个DataSource构造函数:用于独立使用 MyBatis 时,通过数据源创建连接,MyBatis 自行管理事务。
  • 一个 Connection 构造函数:用于 Spring 整合场景,Spring 事务管理器已从数据源获取连接并绑定到当前线程,MyBatis 直接使用该连接。

四、ManagedTransaction — 容器管理事务

ManagedTransaction 将事务的 commit() 和 rollback() 完全委托给容器(如 Spring 或 Java EE 应用服务器),自身不执行任何提交/回滚操作。

// org.apache.ibatis.transaction.managed.ManagedTransactionpublic class ManagedTransaction implements Transaction {    private static final Log log = LogFactory.getLog(ManagedTransaction.class);    private DataSource dataSource;    private TransactionIsolationLevel level;    private Connection connection;    private final boolean closeConnection;   // ⭐ final,构造后不可变    // 构造函数1:从已有 Connection 创建    public ManagedTransaction(Connection connection, boolean closeConnection) {        this.connection = connection;        this.closeConnection = closeConnection;    }    // 构造函数2:从 DataSource 创建    public ManagedTransaction(DataSource ds, TransactionIsolationLevel level, boolean closeConnection) {        this.dataSource = ds;        this.level = level;        this.closeConnection = closeConnection;    }    @Override    public Connection getConnection() throws SQLException {        if (this.connection == null) {            openConnection();   // 延迟获取        }        return this.connection;    }    @Override    public void commit() throws SQLException {        // Does nothing — 事务由容器管理    }    @Override    public void rollback() throws SQLException {        // Does nothing — 事务由容器管理    }    @Override    public void close() throws SQLException {        if (this.closeConnection && this.connection != null) {            if (log.isDebugEnabled()) {                log.debug("Closing JDBC Connection [" + this.connection + "]");            }            this.connection.close();        }    }    protected void openConnection() throws SQLException {        if (log.isDebugEnabled()) {            log.debug("Opening JDBC Connection");        }        this.connection = this.dataSource.getConnection();        if (this.level != null) {            this.connection.setTransactionIsolation(this.level.getLevel());        }    }    @Override    public Integer getTimeout() throws SQLException {        return null;    }}

4.1 关键机制解读

(1)commit()/rollback()为空方法

事务控制权完全交给容器,MyBatis 不做任何干预。这是ManagedTransactionJdbcTransaction最本质的区别。

(2)closeConnection控制连接关闭

  • true(默认):close()时关闭连接,适用于 MyBatis 独立使用。
  • falseclose()时不关闭连接,适用于连接由容器管理且容器负责关闭的场景。

(3)两个构造函数

  • ManagedTransaction(Connection connection, boolean closeConnection):直接传入已有连接,适用于 Spring 整合时从TransactionSynchronizationManager获取当前线程绑定的连接。
  • ManagedTransaction(DataSource ds, TransactionIsolationLevel level, boolean closeConnection):通过 DataSource 获取连接,适用于独立使用场景。

(4)延迟连接获取

getConnection()首次调用时才通过openConnection()获取,避免提前占用数据库连接资源。

4.2 Spring 整合中的实际使用

在 Spring 整合环境中,虽然mybatis-config.xml中通常配置transactionManager="JDBC",但 Spring 的SqlSessionFactoryBean会创建自定义Environment,实际使用的是SpringManagedTransactionFactory。它生成的SpringManagedTransaction实现了Transaction接口。

SpringManagedTransaction 的行为是有条件的

  • 如果 Spring 事务处于活动状态,其 commit()/rollback() 方法为空实现(no-op),事务完全由 Spring 的 PlatformTransactionManager(如 DataSourceTransactionManager)管理
  • 如果 没有 Spring 活动事务,它的行为与 JdbcTransaction 类似,会实际调用 Connection 的 commit()/rollback() 方法。
// SpringManagedTransaction(mybatis-spring 3.x)public class SpringManagedTransaction implements Transaction {    private final DataSource dataSource;    private Connection connection;    private boolean isConnectionTransactional;    @Override    public Connection getConnection() throws SQLException {        if (this.connection == null) {            // 方法稍微整理了一下,未与源码严格一致            // 从 Spring 事务同步器中获取当前线程绑定的连接            this.connection = DataSourceUtils.getConnection(this.dataSource);            this.isConnectionTransactional =                 DataSourceUtils.isConnectionTransactional(this.connection, this.dataSource);        }        return this.connection;    }    @Override    public void commit() throws SQLException {        // 不做任何事情,由 Spring 事务管理器控制    }    @Override    public void rollback() throws SQLException {        // 不做任何事情,由 Spring 事务管理器控制    }}

五、事务与 Executor 的绑定

回顾第 4 篇中 openSessionFromDataSource 的流程:

// DefaultSqlSessionFactoryprivate SqlSession openSessionFromDataSource(ExecutorType execType,                                              TransactionIsolationLevel level,                                              boolean autoCommit) {    Transaction tx = null;    try {        final Environment environment = configuration.getEnvironment();        final TransactionFactory transactionFactory =             getTransactionFactoryFromEnvironment(environment);        // 1. 创建 Transaction        tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);        // 2. 创建 Executor 并传入 Transaction        final Executor executor = configuration.newExecutor(tx, execType);        // 3. 创建 SqlSession        return new DefaultSqlSession(configuration, executor, autoCommit);    } catch (Exception e) {        closeTransaction(tx);        throw ExceptionFactory.wrapException("Error opening session.  Cause: " + e, e);    }}

Executor持有Transaction引用,所有数据库操作都通过Transaction获取连接、提交、回滚。

事务与 SqlSession 的绑定

  • 一个SqlSession对应一个Executor,对应一个Transaction
  • 事务边界 =SqlSession生命周期(从openSessionclose

六、Spring 整合后的事务接管

6.1 核心变革:SqlSessionTemplate

在 Spring 整合环境中,你不再直接使用 DefaultSqlSession,而是通过 SqlSessionTemplate。它内部持有 SqlSessionFactory,并通过 JDK 动态代理控制每次操作的行为。

// org.mybatis.spring.SqlSessionTemplatepublic class SqlSessionTemplate implements SqlSessionDisposableBean {    private final SqlSessionFactory sqlSessionFactory;    private final ExecutorType executorType;    //动态代理    private final SqlSession sqlSessionProxy;      private final PersistenceExceptionTranslator exceptionTranslator;    public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory,                           ExecutorType executorType,                           PersistenceExceptionTranslator exceptionTranslator) {    Assert.notNull(sqlSessionFactory, "Property 'sqlSessionFactory' is required");    Assert.notNull(executorType, "Property 'executorType' is required");    this.sqlSessionFactory = sqlSessionFactory;    this.executorType = executorType;    this.exceptionTranslator = exceptionTranslator;    // 创建动态代理    this.sqlSessionProxy = (SqlSession) Proxy.newProxyInstance(        SqlSessionFactory.class.getClassLoader(),        new Class[]{SqlSession.class},        new SqlSessionInterceptor()    );}}

关键:所有方法调用都会经过 SqlSessionInterceptor,由它决定从 Spring 事务同步器中获取还是新建 SqlSession

6.2 SqlSessionInterceptor 核心逻辑

// org.mybatis.spring.SqlSessionTemplate.SqlSessionInterceptorprivate class SqlSessionInterceptor implements InvocationHandler {    // 私有构造,限制只能在当前外部类中实例化    private SqlSessionInterceptor() {}      @Override    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {        // 1. 从 Spring 事务同步器中获取 SqlSession(若不存在则新建)        SqlSession sqlSession = SqlSessionUtils.getSqlSession(            SqlSessionTemplate.this.sqlSessionFactory,            SqlSessionTemplate.this.executorType,            SqlSessionTemplate.this.exceptionTranslator        );        Object unwrapped;  // 最终返回的结果或异常        try {            // 2. 执行目标方法(调用真实的 SqlSession)            Object result = method.invoke(sqlSession, args);            // 3. 如果该 SqlSession 不在 Spring 事务管理下,立即提交            if (!SqlSessionUtils.isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {                sqlSession.commit(true);            }            unwrapped = result;        } catch (Throwable t) {            // 4. 异常处理(核心!)            unwrapped = ExceptionUtil.unwrapThrowable(t);            // 如果配置了 exceptionTranslator 且异常是 PersistenceException            if (SqlSessionTemplate.this.exceptionTranslator != null && unwrapped instanceof PersistenceException) {                // ⭐ 先关闭 SqlSession(释放连接),再转换异常                SqlSessionUtils.closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);                sqlSession = null;  // 标记已关闭,防止 finally 中重复关闭                Throwable translated = SqlSessionTemplate.this.exceptionTranslator.translateExceptionIfPossible(                    (PersistenceException) unwrapped                );                if (translated != null) {                    unwrapped = translated;  // 替换成 Spring 的 DataAccessException                }            }            // 重新抛出异常(可能是原始异常,也可能是转换后的)            throw (Throwable) unwrapped;        } finally {            // 5. 如果 SqlSession 未被关闭(即没有发生异常转换),在 finally 中关闭            if (sqlSession != null) {                SqlSessionUtils.closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);            }        }    }}

6.3getSqlSession核心逻辑

SqlSessionUtils.getSqlSession 负责从 Spring 的事务同步器中获取或创建 SqlSession

// org.mybatis.spring.SqlSessionUtilspublic static SqlSession getSqlSession(SqlSessionFactory sessionFactory,                                        ExecutorType executorType,                                        PersistenceExceptionTranslator exceptionTranslator) {    Assert.notNull(sessionFactory, "No SqlSessionFactory specified");    Assert.notNull(executorType, "No ExecutorType specified");    // 1. 从当前线程获取 SqlSessionHolder    SqlSessionHolder holder = (SqlSessionHolder)         TransactionSynchronizationManager.getResource(sessionFactory);    // 2. 通过 sessionHolder 方法检查是否可复用    SqlSession session = sessionHolder(executorType, holder);    if (session != null) {        return session;    }    // 3. 创建新的 SqlSession    LOGGER.debug(() -> "Creating a new SqlSession");    session = sessionFactory.openSession(executorType);    // 4. 注册到事务同步器    registerSessionHolder(sessionFactory, executorType, exceptionTranslator, session);    return session;}

6.4 事务同步管理器:TransactionSynchronizationManager

Spring 通过TransactionSynchronizationManager维护当前线程的事务资源,包括:

  • 当前线程绑定的DataSource连接
  • 事务的隔离级别、传播行为等
  • 事务同步回调(如SqlSessionSynchronization

TransactionSynchronizationManager内部使用ThreadLocal存储这些信息,保证了线程隔离。

关键流程

  1. @Transactional方法开启时,Spring 开启事务,从 DataSource 获取连接,绑定到ThreadLocal
  2. 首次执行 Mapper 操作时,SqlSessionInterceptor通过getSqlSession获取SqlSessionHolder(新建并绑定)。
  3. 后续同一事务中的 Mapper 操作,直接从ThreadLocal中获取同一个SqlSession
  4. 事务提交时,SqlSessionSynchronization回调触发SqlSession.commit(),连接随之提交。
  5. 事务回滚时,触发SqlSession.rollback(),然后关闭连接。

七、Spring 整合后为什么不需要手动 commit/rollback

在没有 Spring 事务的情况下(如方法未加 @Transactional),SqlSessionInterceptor 会为每次 Mapper 方法调用创建一个新的 SqlSession,并在方法执行完毕后立即提交并关闭该 SqlSession。这保证了每个数据库操作都是独立、自动提交的,但其行为与原生 MyBatis 的 autoCommit=true 模式有所不同——这里是 SqlSessionTemplate 在每次方法调用级别管理了 SqlSession 的生命周期

SqlSessionInterceptor中的判断

if (!isSqlSessionTransactional(sqlSession, sqlSessionFactory)) {    // 非事务托管:手动提交    sqlSession.commit(true);}// 事务托管:不提交,交给 Spring
  • 当处于@Transactional方法内时,isSqlSessionTransactional返回true,MyBatis 不会提交,由 Spring 管理。
  • 当没有事务时,isSqlSessionTransactional返回false,MyBatis 在每个操作后自动提交。

八、Spring 整合中的事务传播行为

8.1 传播行为对 SqlSession 的影响

核心原则SqlSession 是否复用,取决于当前线程是否已经绑定了一个与 SqlSessionFactory 关联的 SqlSessionHolderSqlSessionUtils.getSqlSession() 方法会优先从 TransactionSynchronizationManager 中获取已绑定的 SqlSession

不同的事务传播行为会影响事务的创建和挂起,从而间接影响SqlSession的获取:

传播行为

对 SqlSession 的影响

REQUIRED

若当前有事务,复用同一个 SqlSession;否则新建一个并绑定到当前线程。

REQUIRES_NEW

挂起当前事务(解绑 SqlSession),新建一个独立事务和新的 SqlSession。新事务提交/回滚后,恢复原事务及其绑定的 SqlSession

NESTED

在现有事务中通过保存点(Savepoint) 实现嵌套。SqlSession可能复用,但具体行为依赖于 Spring 事务管理的实现,不能一概而论。

SUPPORTS

若当前有事务则参与并复用 SqlSession;否则不开启新事务,每次操作可能创建新的 SqlSession

NOT_SUPPORTED

挂起当前事务,以非事务方式执行。当前线程的 SqlSession 被解绑,每次操作可能新建 SqlSession

MANDATORY

必须存在事务,复用当前事务绑定的 SqlSession,否则抛异常。

NEVER

必须在非事务环境下执行。若当前有事务则抛异常,且不会复用 SqlSession

8.2 REQUIRES_NEW 示例

@Servicepublic class OuterService {    @Transactional    public void outerMethod() {        userMapper.selectById(1);    // 使用 SqlSession1        innerService.innerMethod();  // 调用内层    }}@Servicepublic class InnerService {    @Transactional(propagation = Propagation.REQUIRES_NEW)    public void innerMethod() {        orderMapper.selectById(1);   // 使用 SqlSession2(新事务)    }}

执行结果:

  • 外层事务中创建并绑定SqlSession1
  • 内层REQUIRES_NEW挂起外层事务,创建SqlSession2
  • 内层提交时只提交SqlSession2,外层的事务独立

九、面试高频题(附源码级回答)

Q1:MyBatis 原生的JdbcTransactionManagedTransaction有什么区别?

A

  • JdbcTransaction:MyBatis 原生事务实现,直接调用Connection.commit()/rollback(),适合独立使用。close()前会调用resetAutoCommit()autoCommit重置为true,避免某些数据库驱动报错。
  • ManagedTransactioncommit()/rollback()为空方法,事务由容器管理。closeConnection字段控制close()时是否关闭连接。

Q2:JdbcTransactionresetAutoCommit()的作用是什么?

A:某些数据库驱动(如 Sybase)在关闭Connection时,如果当前处于非自动提交模式且事务未提交/回滚,会抛出异常。resetAutoCommit()在关闭连接前将autoCommit重置为true,避免这个问题。skipSetAutoCommitOnClose可控制是否跳过此操作,适用于 Spring 等容器管理连接的场景。

Q3:Spring 整合后,SqlSession是如何与当前线程绑定的?

A:通过 SqlSessionUtils.getSqlSession() 方法,使用 TransactionSynchronizationManager.getResource(sessionFactory) 从当前线程中获取已绑定的 SqlSessionHolderSqlSessionHolder 是对 SqlSession 的包装,内部维护了引用计数,用于管理 SqlSession 的打开与关闭生命周期。如果当前有事务且 SqlSessionHolder 存在,直接复用其中的 SqlSession;否则创建一个新的 SqlSession 并包装为 SqlSessionHolder 绑定到 TransactionSynchronizationManager 中。Spring 事务结束时,通过注册的 SqlSessionSynchronization 回调自动关闭 SqlSession 并解绑。

Q4:@Transactional方法中调用多个 Mapper,会创建多少个SqlSession

A只会创建一个SqlSession。首次调用任何 Mapper 时,SqlSessionInterceptor触发SqlSessionUtils.getSqlSession()创建并绑定到当前线程。后续 Mapper 调用复用同一个SqlSession,确保所有数据库操作在同一个事务上下文和数据库连接中执行。

Q5:sqlSession.commit(true)中的force参数是什么意思?

Aforce 参数用于强制提交,即使当前 SqlSession 没有执行过更新操作(dirty 为 false)。在 Spring 非事务环境下,SqlSessionInterceptor 在每个数据库操作后都会调用 sqlSession.commit(true),这是 MyBatis-Spring 的设计约定——非事务模式下每个操作独立提交,force=true 确保提交流程能够正常完成,即使当前会话没有任何数据变更。

Q6:Spring 整合后,一级缓存还有效吗?

A:有效。Spring 事务内复用同一个SqlSession,因此一级缓存(SqlSession级别)在事务内是共享的。不同事务之间由于SqlSession不同,一级缓存隔离。事务方法内多次查询同一个 key 能命中一级缓存。

Q7:为什么在 Spring 环境中使用SqlSessionTemplate而不直接用DefaultSqlSession

ADefaultSqlSession 是非线程安全的——它本身设计为绑定单个数据库会话,不应被多线程共享。且它无法感知 Spring 的事务上下文。SqlSessionTemplate 是线程安全的,内部通过 ThreadLocal 和动态代理机制,确保每个线程/事务使用正确的 SqlSession 实例。同时,SqlSessionTemplate 还能自动将 MyBatis 的异常转换为 Spring 的 DataAccessException

十、下篇预告

第 12 篇(最终篇)将进行全系列总结 + 面试高频 30 题 + 常见坑点汇总

如果觉得有帮助,欢迎 点赞、在看、转发 支持!