Mybatis

第08篇:Mybatis事务处理

2026-01-294 min readJava 专题系列
Mybatis

一、Jdk底层实现

Java JDK中提供了标准接口Connection,不同的数据库驱动负责具体的实现。后面无论是Spring还是Mybatis对事务的处理,无论怎么的封装,最终究其到底都是由Connection来提供的能力。

java
1public interface Connection extends Wrapper, AutoCloseable { 2 Statement createStatement() throws SQLException; 3 void commit() throws SQLException; 4 void rollback() throws SQLException; 5}

例如 com.mysql.cj.jdbc.ConnectionImpl。具体负责跟mysql进行通信执行命令。

二、Mybatis实现

首先我们来看Mybatis是如何对Connection进行事务的封装。首先我们先来看一个图。

2.1 调用流程

根据上面的图我们看,都是一层一层的封装进行委派最终由Connection的具体数据库驱动来进行实现的。

  • SqlSession
  • Executor
  • Transaction
java
1public interface SqlSession extends Closeable { 2 void commit(); 3 void rollback(); 4} 5public interface Executor { 6 void commit(); 7 void rollback(); 8} 9public interface Transaction { 10 void commit() throws SQLException; 11 void rollback() throws SQLException; 12}

2.2 实现原理

Mybatis中我们的接口是使用代理进行跟数据库进行交互的。所以他的事务提交逻辑是嵌套在代理方法中的。 通过前面的调用流程学习,第04篇:Mybatis代理对象生成我们知道最终都是在MapperMethod对SqlSession的调用执行数据库操作的。 而SqlSession是有两个包装类的。

  • SqlSession 通过底层的封装提供具体的调用指令
  • SqlSessionManager 对SqlSession进行代理,自动对事务进行处理
  • SqlSessionTemplate 事务的处理完全外包给Spring来处理

下面我们分别来看下每个类具体都做了什么吧。

SqlSessionManager

SqlSessionManager 是对SqlSession的一个包装,它会自己来管理SqlSession。他的具体实现是通过对SqlSession 生成代理,代理拦截每个方法进行增强。

java
1 2 private SqlSessionManager(SqlSessionFactory sqlSessionFactory) { 3 this.sqlSessionFactory = sqlSessionFactory; 4 this.sqlSessionProxy = (SqlSession) Proxy.newProxyInstance( 5 SqlSessionFactory.class.getClassLoader(), 6 new Class[]{SqlSession.class}, 7 new SqlSessionInterceptor()); 8 }

SqlSessionInterceptor

java
1 private class SqlSessionInterceptor implements InvocationHandler { 2 public SqlSessionInterceptor() { 3 // Prevent Synthetic Access 4 } 5 6 @Override 7 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { 8 final SqlSession sqlSession = SqlSessionManager.this.localSqlSession.get(); 9 if (sqlSession != null) { 10 try { 11 return method.invoke(sqlSession, args); 12 } catch (Throwable t) { 13 throw ExceptionUtil.unwrapThrowable(t); 14 } 15 } else { 16 try (SqlSession autoSqlSession = openSession()) { 17 try { 18 final Object result = method.invoke(autoSqlSession, args); 19 autoSqlSession.commit(); 20 return result; 21 } catch (Throwable t) { 22 autoSqlSession.rollback(); 23 throw ExceptionUtil.unwrapThrowable(t); 24 } 25 } 26 } 27 } 28 }
  1. 从ThreadLocal中获取SqlSession,如果有,说明是调用方要自己处理事务,那么就只进行执行数据库操作,不进行事务处理和连接的关闭。
  2. 如果没有,说明要自己来管理事务,那么就新生成SqlSession,帮我们调用SqlSession#commit来提交事务,失败进行回滚。

根据其中原理我们知道有两种使用办法,

  • 首先第一种自己管理SqlSession的方式
java
1 InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml"); 2 // 实例化sqlSessionManager 3 SqlSessionManager sqlSessionManager = SqlSessionManager.newInstance(inputStream); 4 // 第一步: 开启管理SqlSession,创建一个SqlSession并存入到ThreadLocal中 5 sqlSessionManager.startManagedSession(); 6 // 使用 7 UserMapper mapper = sqlSessionManager.getMapper(UserMapper.class); 8 mapper.save(new User("孙悟空")); 9 // 第二步: 因为事务是我们自己开启的,所以要自己来操作提交事务,或者回滚 10 sqlSessionManager.commit(); 11 // 第三步: 关闭连接 12 sqlSessionManager.close();
  • 第二种,自动管理SqlSession
java
1 InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml"); 2 // 实例化sqlSessionManager 3 SqlSessionManager sqlSessionManager = SqlSessionManager.newInstance(inputStream); 4 UserMapper mapper = sqlSessionManager.getMapper(UserMapper.class); 5 mapper.save(new User("孙悟空")); 6 // 只用关心关闭就好了,事务的信息,都帮我们完成了。 7 sqlSessionManager.close();

SqlSessionTemplate

线程安全、Spring 管理、与 Spring 事务管理一起使用的SqlSession ,以确保实际使用的 SqlSession 是与当前 Spring 事务关联的那个。此外,它还管理会话生命周期,包括根据 Spring 事务配置根据需要关闭、提交或回滚会话。 模板需要一个 SqlSessionFactory 来创建 SqlSession,作为构造函数参数传递。也可以构造指示要使用的执行器类型,如果没有,将使用会话工厂中定义的默认执行器类型。 此模板将 MyBatis PersistenceExceptions 转换为未经检查的 DataAccessExceptions,默认情况下使用MyBatisExceptionTranslator 。

==SqlSessionTemplate== 和 ==SqlSessionManager==

  • 相同点: 都是通过对SqlSession进行代理对方法进行增强的
  • 不同点: 前者是将SqlSession外包给Spring进行管理的,后者是自己通过ThreadLocal进行管理的。

下面我们来具体看下是如何拦截增强的。

  1. 第一个点获取SqlSession不同。
    • 从Spring中的事务管理器中获取当前线程的事务信息
  2. 第二个点方法执行完成后都会自动关闭SqlSession或减少引用
    • 为解决嵌套事务的情况,每次执行完后会减少一次引用。当引用都减少为0才会真正进行关闭。
  3. 第三个点是否提交事务,有判定规则。
    • 只有Spring事务管理器中没有事务时候才会自己进行提交,否则都外包给Spring进行管理。

下面我们具体来看下代码的实现吧。

java
1 private class SqlSessionInterceptor implements InvocationHandler { 2 @Override 3 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { 4 SqlSession sqlSession = getSqlSession(SqlSessionTemplate.this.sqlSessionFactory, 5 SqlSessionTemplate.this.executorType, SqlSessionTemplate.this.exceptionTranslator); 6 try { 7 Object result = method.invoke(sqlSession, args); 8 if (!isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) { 9 // force commit even on non-dirty sessions because some databases require 10 // a commit/rollback before calling close() 11 sqlSession.commit(true); 12 } 13 return result; 14 } catch (Throwable t) { 15 Throwable unwrapped = unwrapThrowable(t); 16 if (SqlSessionTemplate.this.exceptionTranslator != null && unwrapped instanceof PersistenceException) { 17 // release the connection to avoid a deadlock if the translator is no loaded. See issue #22 18 closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory); 19 sqlSession = null; 20 Throwable translated = SqlSessionTemplate.this.exceptionTranslator 21 .translateExceptionIfPossible((PersistenceException) unwrapped); 22 if (translated != null) { 23 unwrapped = translated; 24 } 25 } 26 throw unwrapped; 27 } finally { 28 if (sqlSession != null) { 29 closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory); 30 } 31 } 32 } 33 }

getSqlSession

  • 从Spring提供的事务管理器(TransactionSynchronizationManager)中获取当前线程拥有的SqlSession
  • 如果没有就新建一个并注册到TransactionSynchronizationManager上。
java
1 public static SqlSession getSqlSession(SqlSessionFactory sessionFactory, ExecutorType executorType, 2 PersistenceExceptionTranslator exceptionTranslator) { 3 4 notNull(sessionFactory, NO_SQL_SESSION_FACTORY_SPECIFIED); 5 notNull(executorType, NO_EXECUTOR_TYPE_SPECIFIED); 6 // 从Spring提供的事务管理器(TransactionSynchronizationManager)中获取当前线程拥有的SqlSession 7 // 逻辑很简单key=SqlSessionFactory value=SqlSessionHolder 8 SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory); 9 10 SqlSession session = sessionHolder(executorType, holder); 11 if (session != null) { 12 return session; 13 } 14 15 LOGGER.debug(() -> "Creating a new SqlSession"); 16 session = sessionFactory.openSession(executorType); 17 // 如果没有就新建一个并注册到TransactionSynchronizationManager上。 18 registerSessionHolder(sessionFactory, executorType, exceptionTranslator, session); 19 20 return session; 21 }

registerSessionHolder

  • 为了保险先判断下当前线程中是否已经存在同步器,如果存在还注册就提示: "SqlSession [" + session + "] was not registered for synchronization because synchronization is not active");
  • 如果当前线程没有,判断事务管理器是否是SpringManagedTransactionFactory,如果是就注册一个。
  • SqlSessionHolder#requested() 注意这一行,创建后给引用次数加1.
java
1private static void registerSessionHolder(SqlSessionFactory sessionFactory, ExecutorType executorType, 2 PersistenceExceptionTranslator exceptionTranslator, SqlSession session) { 3 SqlSessionHolder holder; 4 if (TransactionSynchronizationManager.isSynchronizationActive()) { 5 Environment environment = sessionFactory.getConfiguration().getEnvironment(); 6 7 if (environment.getTransactionFactory() instanceof SpringManagedTransactionFactory) { 8 LOGGER.debug(() -> "Registering transaction synchronization for SqlSession [" + session + "]"); 9 10 holder = new SqlSessionHolder(session, executorType, exceptionTranslator); 11 TransactionSynchronizationManager.bindResource(sessionFactory, holder); 12 TransactionSynchronizationManager 13 .registerSynchronization(new SqlSessionSynchronization(holder, sessionFactory)); 14 holder.setSynchronizedWithTransaction(true); 15 holder.requested(); 16 } else { 17 if (TransactionSynchronizationManager.getResource(environment.getDataSource()) == null) { 18 LOGGER.debug(() -> "SqlSession [" + session 19 + "] was not registered for synchronization because DataSource is not transactional"); 20 } else { 21 throw new TransientDataAccessResourceException( 22 "SqlSessionFactory must be using a SpringManagedTransactionFactory in order to use Spring transaction synchronization"); 23 } 24 } 25 } else { 26 LOGGER.debug(() -> "SqlSession [" + session 27 + "] was not registered for synchronization because synchronization is not active"); 28 }

closeSqlSession

  • 如果是Spring的事务管理,就减少引用
  • 如果不是Spring的事务管理,就直接关闭
java
1 public static void closeSqlSession(SqlSession session, SqlSessionFactory sessionFactory) { 2 notNull(session, NO_SQL_SESSION_SPECIFIED); 3 notNull(sessionFactory, NO_SQL_SESSION_FACTORY_SPECIFIED); 4 5 SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory); 6 if ((holder != null) && (holder.getSqlSession() == session)) { 7 LOGGER.debug(() -> "Releasing transactional SqlSession [" + session + "]"); 8 holder.released(); 9 } else { 10 LOGGER.debug(() -> "Closing non transactional SqlSession [" + session + "]"); 11 session.close(); 12 } 13 }

isSqlSessionTransactional

事务的判定逻辑:

  • 如果从事务管理器中获取,说明当前线程是有事务的
  • 当前线程中的事务SqlSession和这个方法中的SqlSession是同一个,说明是嵌套事务。

如果是Spring来管理事务,这就不会自动来提交事务。外包给Spring的事务拦截器自己去处理。

java
1 public static boolean isSqlSessionTransactional(SqlSession session, SqlSessionFactory sessionFactory) { 2 notNull(session, NO_SQL_SESSION_SPECIFIED); 3 notNull(sessionFactory, NO_SQL_SESSION_FACTORY_SPECIFIED); 4 5 SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory); 6 7 return (holder != null) && (holder.getSqlSession() == session); 8 }

好了,到这里Mybatis中事务的处理逻辑我们就到了解了。

SqlSession对底层进行封装提供具体的指令 SqlSessionManager和SqlSessionTemplate都是对SqlSession进行增强来自动或者委派Spring进行事务的处理的。

下面我们去看看Spring是如何来处理事务的吧。Spring事务的处理方式