# 精尽 MyBatis 源码解析 —— Spring 集成(四)之事务 # 1. 概述 本文我们就来看看,Spring 和 MyBatis 的**事务**是如何集成。需要胖友阅读的前置文章是: - [《精尽 MyBatis 源码分析 —— 事务模块》](http://svip.iocoder.cn/MyBatis/transaction-package/) - [《精尽 Spring 源码分析 —— Transaction 源码简单导读》](http://svip.iocoder.cn/Spring/transaction-simple-intro/) 需要胖友对 Spring Transaction 有源码级的了解,否则对该文章,会有点懵逼。 # 2. SpringManagedTransaction `org.mybatis.spring.transaction.SpringManagedTransaction` ,实现 `org.apache.ibatis.transaction.Transaction` 接口,Spring 托管事务的 Transaction 实现类。 ## 2.1 构造方法 ``` // SpringManagedTransaction.java /** * DataSource 对象 */ private final DataSource dataSource; /** * Connection 对象 */ private Connection connection; /** * 当前连接是否处于事务中 * * @see DataSourceUtils#isConnectionTransactional(Connection, DataSource) */ private boolean isConnectionTransactional; /** * 是否自动提交 */ private boolean autoCommit; public SpringManagedTransaction(DataSource dataSource) { notNull(dataSource, "No DataSource specified"); this.dataSource = dataSource; } ``` ## 2.2 getConnection `#getConnection()` 方法,获得连接。代码如下: ``` // SpringManagedTransaction.java @Override public Connection getConnection() throws SQLException { if (this.connection == null) { // 如果连接不存在,获得连接 openConnection(); } return this.connection; } ``` - 如果 `connection` 为空,则调用 `#openConnection()` 方法,获得连接。代码如下: ``` // SpringManagedTransaction.java /** * Gets a connection from Spring transaction manager and discovers if this * {@code Transaction} should manage connection or let it to Spring. *

* It also reads autocommit setting because when using Spring Transaction MyBatis * thinks that autocommit is always false and will always call commit/rollback * so we need to no-op that calls. */ private void openConnection() throws SQLException { // 获得连接 this.connection = DataSourceUtils.getConnection(this.dataSource); this.autoCommit = this.connection.getAutoCommit(); this.isConnectionTransactional = DataSourceUtils.isConnectionTransactional(this.connection, this.dataSource); LOGGER.debug(() -> "JDBC Connection [" + this.connection + "] will" + (this.isConnectionTransactional ? " " : " not ") + "be managed by Spring"); } ``` - 比较有趣的是,此处获取连接,不是通过 `DataSource#getConnection()` 方法,而是通过 `org.springframework.jdbc.datasource.DataSourceUtils#getConnection(DataSource dataSource)` 方法,获得 Connection 对象。而实际上,基于 Spring Transaction 体系,如果此处正在**事务中**时,已经有和当前线程绑定的 Connection 对象,就是存储在 ThreadLocal 中。 ## 2.3 commit `#commit()` 方法,提交事务。代码如下: ``` // SpringManagedTransaction.java @Override public void commit() throws SQLException { if (this.connection != null && !this.isConnectionTransactional && !this.autoCommit) { LOGGER.debug(() -> "Committing JDBC Connection [" + this.connection + "]"); this.connection.commit(); } } ``` ## 2.4 rollback `#rollback()` 方法,回滚事务。代码如下: ``` // SpringManagedTransaction.java @Override public void rollback() throws SQLException { if (this.connection != null && !this.isConnectionTransactional && !this.autoCommit) { LOGGER.debug(() -> "Rolling back JDBC Connection [" + this.connection + "]"); this.connection.rollback(); } } ``` ## 2.5 close `#close()` 方法,释放连接。代码如下: ``` // SpringManagedTransaction.java @Override public void close() throws SQLException { DataSourceUtils.releaseConnection(this.connection, this.dataSource); } ``` - 比较有趣的是,此处获取连接,不是通过 `Connection#close()` 方法,而是通过 `org.springframework.jdbc.datasource.DataSourceUtils#releaseConnection(Connection connection,DataSource dataSource)` 方法,“释放”连接。但是,具体会不会关闭连接,根据当前线程绑定的 Connection 对象,是不是传入的 `connection` 参数。 ## 2.6 getTimeout ``` // SpringManagedTransaction.java @Override public Integer getTimeout() throws SQLException { ConnectionHolder holder = (ConnectionHolder) TransactionSynchronizationManager.getResource(dataSource); if (holder != null && holder.hasTimeout()) { return holder.getTimeToLiveInSeconds(); } return null; } ``` # 3. SpringManagedTransactionFactory `org.mybatis.spring.transaction.SpringManagedTransactionFactory` ,实现 TransactionFactory 接口,SpringManagedTransaction 的工厂实现类。代码如下: ``` // SpringManagedTransactionFactory.java public class SpringManagedTransactionFactory implements TransactionFactory { @Override public Transaction newTransaction(DataSource dataSource, TransactionIsolationLevel level, boolean autoCommit) { // 创建 SpringManagedTransaction 对象 return new SpringManagedTransaction(dataSource); } @Override public Transaction newTransaction(Connection conn) { // 抛出异常,因为 Spring 事务,需要一个 DataSource 对象 throw new UnsupportedOperationException("New Spring transactions require a DataSource"); } @Override public void setProperties(Properties props) { // not needed in this version } } ``` # 4. SqlSessionHolder `org.mybatis.spring.SqlSessionHolder` ,继承 `org.springframework.transaction.support.ResourceHolderSupport` 抽象类,SqlSession 持有器,用于保存当前 SqlSession 对象,保存到 `org.springframework.transaction.support.TransactionSynchronizationManager` 中。代码如下: ``` // SqlSessionHolder.java /** * Used to keep current {@code SqlSession} in {@code TransactionSynchronizationManager}. * * The {@code SqlSessionFactory} that created that {@code SqlSession} is used as a key. * {@code ExecutorType} is also kept to be able to check if the user is trying to change it * during a TX (that is not allowed) and throw a Exception in that case. * * SqlSession 持有器,用于保存当前 SqlSession 对象,保存到 TransactionSynchronizationManager 中 * * @author Hunter Presnall * @author Eduardo Macarron */ public final class SqlSessionHolder extends ResourceHolderSupport { /** * SqlSession 对象 */ private final SqlSession sqlSession; /** * 执行器类型 */ private final ExecutorType executorType; /** * PersistenceExceptionTranslator 对象 */ private final PersistenceExceptionTranslator exceptionTranslator; /** * Creates a new holder instance. * * @param sqlSession the {@code SqlSession} has to be hold. * @param executorType the {@code ExecutorType} has to be hold. * @param exceptionTranslator the {@code PersistenceExceptionTranslator} has to be hold. */ public SqlSessionHolder(SqlSession sqlSession, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator) { notNull(sqlSession, "SqlSession must not be null"); notNull(executorType, "ExecutorType must not be null"); this.sqlSession = sqlSession; this.executorType = executorType; this.exceptionTranslator = exceptionTranslator; } // ... 省略 getting 方法 } ``` - 当存储到 TransactionSynchronizationManager 中时,使用的 KEY 为创建该 SqlSession 对象的 SqlSessionFactory 对象。详细解析,见 `SqlSessionUtils#registerSessionHolder(...)` 方法。 # 5. SqlSessionUtils `org.mybatis.spring.SqlSessionUtils` ,SqlSession 工具类。它负责处理 MyBatis SqlSession 的生命周期。它可以从 Spring TransactionSynchronizationManager 中,注册和获得对应的 SqlSession 对象。同时,它也支持当前不处于事务的情况下。 😈 当然,这个描述看起来,有点绕。所以,我们直接干起代码。 ## 5.1 构造方法 ``` // SqlSessionUtils.java private static final String NO_EXECUTOR_TYPE_SPECIFIED = "No ExecutorType specified"; private static final String NO_SQL_SESSION_FACTORY_SPECIFIED = "No SqlSessionFactory specified"; private static final String NO_SQL_SESSION_SPECIFIED = "No SqlSession specified"; /** * This class can't be instantiated, exposes static utility methods only. */ private SqlSessionUtils() { // do nothing } ``` - 空的,直接跳过。 ## 5.2 getSqlSession `#getSqlSession(SqlSessionFactory sessionFactory, ...)` 方法,获得 SqlSession 对象。代码如下: ``` // SqlSessionUtils.java /** * Creates a new MyBatis {@code SqlSession} from the {@code SqlSessionFactory} * provided as a parameter and using its {@code DataSource} and {@code ExecutorType} * * @param sessionFactory a MyBatis {@code SqlSessionFactory} to create new sessions * @return a MyBatis {@code SqlSession} * @throws TransientDataAccessResourceException if a transaction is active and the * {@code SqlSessionFactory} is not using a {@code SpringManagedTransactionFactory} */ public static SqlSession getSqlSession(SqlSessionFactory sessionFactory) { // 获得执行器类型 ExecutorType executorType = sessionFactory.getConfiguration().getDefaultExecutorType(); // 获得 SqlSession 对象 return getSqlSession(sessionFactory, executorType, null); } /** * Gets an SqlSession from Spring Transaction Manager or creates a new one if needed. * Tries to get a SqlSession out of current transaction. If there is not any, it creates a new one. * Then, it synchronizes the SqlSession with the transaction if Spring TX is active and * SpringManagedTransactionFactory is configured as a transaction manager. * * @param sessionFactory a MyBatis {@code SqlSessionFactory} to create new sessions * @param executorType The executor type of the SqlSession to create * @param exceptionTranslator Optional. Translates SqlSession.commit() exceptions to Spring exceptions. * @return an SqlSession managed by Spring Transaction Manager * @throws TransientDataAccessResourceException if a transaction is active and the * {@code SqlSessionFactory} is not using a {@code SpringManagedTransactionFactory} * @see SpringManagedTransactionFactory */ public static SqlSession getSqlSession(SqlSessionFactory sessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator) { notNull(sessionFactory, NO_SQL_SESSION_FACTORY_SPECIFIED); notNull(executorType, NO_EXECUTOR_TYPE_SPECIFIED); // <1> 获得 SqlSessionHolder 对象 SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory); // <2.1> 获得 SqlSession 对象 SqlSession session = sessionHolder(executorType, holder); if (session != null) { // <2.2> 如果非空,直接返回 return session; } LOGGER.debug(() -> "Creating a new SqlSession"); // <3.1> 创建 SqlSession 对象 session = sessionFactory.openSession(executorType); // <3.2> 注册到 TransactionSynchronizationManager 中 registerSessionHolder(sessionFactory, executorType, exceptionTranslator, session); return session; } ``` - 我们先看看每一步的作用,然后胖友在自己看下上面的英文注释,嘿嘿。 - `<1>` 处,调用 `TransactionSynchronizationManager#getResource(sessionFactory)` 方法,获得 SqlSessionHolder 对象。为什么可以获取到呢?答案在 `<3.2>` 中。关于 TransactionSynchronizationManager 类,如果不熟悉的胖友,真的真的真的,先去看懂 Spring Transaction 体系。 - `<2.1>` 处,调用 `#sessionHolder(ExecutorType executorType, SqlSessionHolder holder)` 方法,从 SqlSessionHolder 中,获得 SqlSession 对象。代码如下: ``` // SqlSessionUtils.java private static SqlSession sessionHolder(ExecutorType executorType, SqlSessionHolder holder) { SqlSession session = null; if (holder != null && holder.isSynchronizedWithTransaction()) { // 如果执行器类型发生了变更,抛出 TransientDataAccessResourceException 异常 if (holder.getExecutorType() != executorType) { throw new TransientDataAccessResourceException("Cannot change the ExecutorType when there is an existing transaction"); } // <1> 增加计数 holder.requested(); LOGGER.debug(() -> "Fetched SqlSession [" + holder.getSqlSession() + "] from current transaction"); // <2> 获得 SqlSession 对象 session = holder.getSqlSession(); } return session; } ``` - `<1>` 处,调用 `SqlSessionHolder#requested()` 方法,增加计数。注意,这个的计数,是用于关闭 SqlSession 时使用。详细解析,见 TODO 方法。 - `<2>` 处,调用 `SqlSessionHolder#getSqlSession()` 方法,获得 SqlSession 对象。 - `<2.2>` 处,如果非空,直接返回。 - `<3.1>` 处,调用 `SqlSessionFactory#openSession(executorType)` 方法,创建 SqlSession 对象。其中,使用的执行器类型,由传入的 `executorType` 方法参数所决定。 - `<3.2>` 处,调用 `#registerSessionHolder(SqlSessionFactory sessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator, SqlSession session)` 方法,注册 SqlSession 对象,到 TransactionSynchronizationManager 中。代码如下: ``` // SqlSessionUtils.java /** * Register session holder if synchronization is active (i.e. a Spring TX is active). * * Note: The DataSource used by the Environment should be synchronized with the * transaction either through DataSourceTxMgr or another tx synchronization. * Further assume that if an exception is thrown, whatever started the transaction will * handle closing / rolling back the Connection associated with the SqlSession. * * @param sessionFactory sqlSessionFactory used for registration. * @param executorType executorType used for registration. * @param exceptionTranslator persistenceExceptionTranslator used for registration. * @param session sqlSession used for registration. */ private static void registerSessionHolder(SqlSessionFactory sessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator, SqlSession session) { SqlSessionHolder holder; if (TransactionSynchronizationManager.isSynchronizationActive()) { Environment environment = sessionFactory.getConfiguration().getEnvironment(); // <1> 如果使用 Spring 事务管理器 if (environment.getTransactionFactory() instanceof SpringManagedTransactionFactory) { LOGGER.debug(() -> "Registering transaction synchronization for SqlSession [" + session + "]"); // <1.1> 创建 SqlSessionHolder 对象 holder = new SqlSessionHolder(session, executorType, exceptionTranslator); // <1.2> 绑定到 TransactionSynchronizationManager 中 TransactionSynchronizationManager.bindResource(sessionFactory, holder); // <1.3> 创建 SqlSessionSynchronization 到 TransactionSynchronizationManager 中 TransactionSynchronizationManager.registerSynchronization(new SqlSessionSynchronization(holder, sessionFactory)); // <1.4> 设置同步 holder.setSynchronizedWithTransaction(true); // <1.5> 增加计数 holder.requested(); // <2> 如果非 Spring 事务管理器,抛出 TransientDataAccessResourceException 异常 } else { TransactionSynchronizationManager.getResource(environment.getDataSource()); throw new TransientDataAccessResourceException( "SqlSessionFactory must be using a SpringManagedTransactionFactory in order to use Spring transaction synchronization"); } } else { LOGGER.debug(() -> "SqlSession [" + session + "] was not registered for synchronization because synchronization is not active"); } } ``` - `<2>` 处,如果非 Spring 事务管理器,抛出 TransientDataAccessResourceException 异常。 - ``` <1> ``` 处,如果使用 Spring 事务管理器( SpringManagedTransactionFactory ),则进行注册。 - `<1.1>` 处,将 `sqlSession` 封装成 SqlSessionHolder 对象。 - `<1.2>` 处,调用 `TransactionSynchronizationManager#bindResource(sessionFactory, holder)` 方法,绑定 `holder` 到 TransactionSynchronizationManager 中。**注意**,此时的 KEY 是 `sessionFactory` ,就创建 `sqlSession` 的 SqlSessionFactory 对象。 - `<1.3>` 处,创建 SqlSessionSynchronization 到 TransactionSynchronizationManager 中。详细解析,见 [「6. SqlSessionSynchronization」](https://svip.iocoder.cn/MyBatis/Spring-Integration-4/#) 。 - `<1.4>` 处,设置同步。 - `<1.5>` 处,调用 `SqlSessionHolder#requested()` 方法,增加计数。 ## 5.3 isSqlSessionTransactional `#isSqlSessionTransactional(SqlSession session, SqlSessionFactory sessionFactory)` 方法,判断传入的 SqlSession 参数,是否在 Spring 事务中。代码如下: ``` // SqlSessionUtils.java /** * Returns if the {@code SqlSession} passed as an argument is being managed by Spring * * @param session a MyBatis SqlSession to check * @param sessionFactory the SqlSessionFactory which the SqlSession was built with * @return true if session is transactional, otherwise false */ public static boolean isSqlSessionTransactional(SqlSession session, SqlSessionFactory sessionFactory) { notNull(session, NO_SQL_SESSION_SPECIFIED); notNull(sessionFactory, NO_SQL_SESSION_FACTORY_SPECIFIED); // 从 TransactionSynchronizationManager 中,获得 SqlSessionHolder 对象 SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory); // 如果相等,说明在 Spring 托管的事务中 return (holder != null) && (holder.getSqlSession() == session); } ``` - 代码比较简单,直接看注释。 ## 5.4 closeSqlSession `#closeSqlSession(SqlSession session, SqlSessionFactory sessionFactory)` 方法,关闭 SqlSession 对象。代码如下: ``` // SqlSessionUtils.java /** * Checks if {@code SqlSession} passed as an argument is managed by Spring {@code TransactionSynchronizationManager} * If it is not, it closes it, otherwise it just updates the reference counter and * lets Spring call the close callback when the managed transaction ends * * @param session a target SqlSession * @param sessionFactory a factory of SqlSession */ public static void closeSqlSession(SqlSession session, SqlSessionFactory sessionFactory) { notNull(session, NO_SQL_SESSION_SPECIFIED); notNull(sessionFactory, NO_SQL_SESSION_FACTORY_SPECIFIED); // <1> 从 TransactionSynchronizationManager 中,获得 SqlSessionHolder 对象 SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory); // <2.1> 如果相等,说明在 Spring 托管的事务中,则释放 holder 计数 if ((holder != null) && (holder.getSqlSession() == session)) { LOGGER.debug(() -> "Releasing transactional SqlSession [" + session + "]"); holder.released(); // <2.2> 如果不相等,说明不在 Spring 托管的事务中,直接关闭 SqlSession 对象 } else { LOGGER.debug(() -> "Closing non transactional SqlSession [" + session + "]"); session.close(); } } ``` - `<1>` 处,从 TransactionSynchronizationManager 中,获得 SqlSessionHolder 对象。 - `<2.1>` 处,如果相等,说明在 Spring 托管的事务中,则释放 `holder` 计数。那有什么用呢?具体原因,见 [「6. SqlSessionSynchronization」](https://svip.iocoder.cn/MyBatis/Spring-Integration-4/#) 。 - `<2.2>` 处,如果不相等,说明不在 Spring 托管的事务中,直接关闭 SqlSession 对象。 # 6. SqlSessionSynchronization SqlSessionSynchronization ,是 SqlSessionUtils 的内部类,继承 TransactionSynchronizationAdapter 抽象类,SqlSession 的 同步器,基于 Spring Transaction 体系。 ``` /** * Callback for cleaning up resources. It cleans TransactionSynchronizationManager and * also commits and closes the {@code SqlSession}. * It assumes that {@code Connection} life cycle will be managed by * {@code DataSourceTransactionManager} or {@code JtaTransactionManager} */ ``` ## 6.1 构造方法 ``` // SqlSessionSynchronization.java /** * SqlSessionHolder 对象 */ private final SqlSessionHolder holder; /** * SqlSessionFactory 对象 */ private final SqlSessionFactory sessionFactory; /** * 是否开启 */ private boolean holderActive = true; public SqlSessionSynchronization(SqlSessionHolder holder, SqlSessionFactory sessionFactory) { notNull(holder, "Parameter 'holder' must be not null"); notNull(sessionFactory, "Parameter 'sessionFactory' must be not null"); this.holder = holder; this.sessionFactory = sessionFactory; } ``` ## 6.2 getOrder ``` // SqlSessionSynchronization.java @Override public int getOrder() { // order right before any Connection synchronization return DataSourceUtils.CONNECTION_SYNCHRONIZATION_ORDER - 1; } ``` ## 6.3 suspend `#suspend()` 方法,当事务挂起时,取消当前线程的绑定的 SqlSessionHolder 对象。代码如下: ``` // SqlSessionSynchronization.java @Override public void suspend() { if (this.holderActive) { LOGGER.debug(() -> "Transaction synchronization suspending SqlSession [" + this.holder.getSqlSession() + "]"); TransactionSynchronizationManager.unbindResource(this.sessionFactory); } } ``` ## 6.4 resume `#resume()` 方法,当事务恢复时,重新绑定当前线程的 SqlSessionHolder 对象。代码如下: ``` // SqlSessionSynchronization.java public void resume() { if (this.holderActive) { LOGGER.debug(() -> "Transaction synchronization resuming SqlSession [" + this.holder.getSqlSession() + "]"); TransactionSynchronizationManager.bindResource(this.sessionFactory, this.holder); } } ``` - 因为,当前 SqlSessionSynchronization 对象中,有 `holder` 对象,所以可以直接恢复。 ## 6.5 beforeCommit `#beforeCommit(boolean readOnly)` 方法,在事务提交之前,调用 `SqlSession#commit()` 方法,提交事务。虽然说,Spring 自身也会调用 `Connection#commit()` 方法,进行事务的提交。但是,`SqlSession#commit()` 方法中,不仅仅有事务的提交,还有提交批量操作,刷新本地缓存等等。代码如下: ``` // SqlSessionSynchronization.java @Override public void beforeCommit(boolean readOnly) { // Connection commit or rollback will be handled by ConnectionSynchronization or // DataSourceTransactionManager. // But, do cleanup the SqlSession / Executor, including flushing BATCH statements so // they are actually executed. // SpringManagedTransaction will no-op the commit over the jdbc connection // TODO This updates 2nd level caches but the tx may be rolledback later on! if (TransactionSynchronizationManager.isActualTransactionActive()) { try { LOGGER.debug(() -> "Transaction synchronization committing SqlSession [" + this.holder.getSqlSession() + "]"); // 提交事务 this.holder.getSqlSession().commit(); } catch (PersistenceException p) { // 如果发生异常,则进行转换,并抛出异常 if (this.holder.getPersistenceExceptionTranslator() != null) { DataAccessException translated = this.holder .getPersistenceExceptionTranslator() .translateExceptionIfPossible(p); throw translated; } throw p; } } } ``` - 耐心的看看英文注释,更有助于理解该方法。 ## 6.6 beforeCompletion > 老艿艿:TransactionSynchronization 的事务提交的执行顺序是:beforeCommit => beforeCompletion => 提交操作 => afterCompletion => afterCommit 。 `#beforeCompletion()` 方法,提交事务完成之前,关闭 SqlSession 对象。代码如下: ``` // SqlSessionSynchronization.java @Override public void beforeCompletion() { // Issue #18 Close SqlSession and deregister it now // because afterCompletion may be called from a different thread if (!this.holder.isOpen()) { LOGGER.debug(() -> "Transaction synchronization deregistering SqlSession [" + this.holder.getSqlSession() + "]"); // 取消当前线程的绑定的 SqlSessionHolder 对象 TransactionSynchronizationManager.unbindResource(sessionFactory); // 标记无效 this.holderActive = false; LOGGER.debug(() -> "Transaction synchronization closing SqlSession [" + this.holder.getSqlSession() + "]"); // 关闭 SqlSession 对象 this.holder.getSqlSession().close(); } } ``` - 因为,beforeCompletion 方法是在 beforeCommit 之后执行,并且在 beforeCommit 已经提交了事务,所以此处可以放心关闭 SqlSession 对象了。 - 要执行关闭操作之前,需要先调用 `SqlSessionHolder#isOpen()` 方法来判断,是否处于开启状态。代码如下: ``` // ResourceHolderSupport.java public boolean isOpen() { return (this.referenceCount > 0); } ``` - 这就是,我们前面看到的各种计数增减的作用。 ## 6.7 afterCompletion `#afterCompletion()` 方法,解决可能出现的**跨线程**的情况,简单理解下就好。代码如下: ``` // ResourceHolderSupport.java @Override public void afterCompletion(int status) { if (this.holderActive) { // 处于有效状态 // afterCompletion may have been called from a different thread // so avoid failing if there is nothing in this one LOGGER.debug(() -> "Transaction synchronization deregistering SqlSession [" + this.holder.getSqlSession() + "]"); // 取消当前线程的绑定的 SqlSessionHolder 对象 TransactionSynchronizationManager.unbindResourceIfPossible(sessionFactory); // 标记无效 this.holderActive = false; LOGGER.debug(() -> "Transaction synchronization closing SqlSession [" + this.holder.getSqlSession() + "]"); // 关闭 SqlSession 对象 this.holder.getSqlSession().close(); } this.holder.reset(); } ``` - 😈 貌似,官方没对这块做单元测试。 # 666. 彩蛋 越写越清晰,哈哈哈哈。 参考和推荐如下文章: - fifadxj [《mybatis-spring事务处理机制分析》](https://my.oschina.net/fifadxj/blog/785621) 提供了序列图