目录
- 一、说明
- 二、TransactionInterceptor开启事务
-
- (1)、拦截方法
- (2)、开启事务绑定数据库连接
- (3)、mybatis中sql执行数据库连接获取
- (4)、事务提交后,当前线程ThreadLocal清理,sqlSession关闭
- 三、总结
一、说明
接着上一个博客SpringBoot 声明式事务 源码解析,下面看一下事务开启后把当前数据库连接绑定到ThreadLocal中,mybatis执行数据库操作时,从ThreadLocal中获取连接执行sql,最后拦截器提交或回滚事务,执行sqlsession(一个sqlsession对应一个数据库连接)提交或回滚,然后清理ThreadLocal,关闭sqlsession,数据库连接回收到数据库连接池。
二、TransactionInterceptor开启事务
(1)、拦截方法
java
@Override
@Nullable
public Object invoke(MethodInvocation invocation) throws Throwable {
// Work out the target class: may be {@code null}.
// The TransactionAttributeSource should be passed the target class
// as well as the method, which may be from an interface.
Class<?> targetClass = (invocation.getThis() != null ? AopUtils.getTargetClass(invocation.getThis()) : null);
// Adapt to TransactionAspectSupport's invokeWithinTransaction...
return invokeWithinTransaction(invocation.getMethod(), targetClass, invocation::proceed);
}
执行invokeWithinTransaction方法,如下图,1是获取目标方法上配置的隔离级别和传播属性等属性,2是开启事务的具体方法,3是继续执行后续拦截器最终执行目标方。
下面重点看2方法中的内容。
继续看status = tm.getTransaction(txAttr);
(2)、开启事务绑定数据库连接
下面跟踪方法时,重点看开启事务和绑定数据库连接到TreadLocal中的内容。
在看代码之前,先看看自动配置类中创建了DataSourceTransactionManager组件,不明白什么时候创建的可以看一下我上个博客。组件中放入了dataSource数据源,若依的框架中添加了动态数据源的配置。
下图是status = tm.getTransaction(txAttr);方法中开启事务的内容。doBegin方法时绑定数据库连接到当前线程。doBegin下面prepareSynchronization里面也很重要,记录一下往TransactionSynchronizationManager中设置了许多参数后面会用到,看一下 TransactionSynchronizationManager.initSynchronization();
下图第一个箭头设置了ActualTransctionActive=true在提交事务的时候会用到。往synchronizations放入了空集合,synchronizations是一个ThreadLocal。
下面看一下doBegin方法。
下面是从数据源中获取连接,把连接设置到了txObject的ConnectionHolder数据库连接描述对象中,后续还会从这里面取出。设置了newConnectionHolder=true.
1、先判断连接是不是自动提交,如果是自动提交会设置成不可以自动提交。2、把事务可用状态设置成true,后续会用到。3、把当前连接信息绑定到当前线程。
可以看到resources是ThreadLocal,ThreadLocal中放入了Map集合,key是动态数据源,value是数据库连接描述对象ConnectionHolder。
(3)、mybatis中sql执行数据库连接获取
MybatisAutoConfiguration中会自动注入SqlSessionTemplate组件,@MapperScan中引入了ClassPathMapperScanner组件,组件扫描所有mapper.java文件,把接口设置成MapperFactoryBean类型的组件,可以一下我以前的博客Spring如何管理Mapper,在设置bean的描述时,也设置了SqlSessionTemplate组件到Mapper中,执行到Configuration.addMapper时,knownMappers.put(type, new MapperProxyFactory(type));往map中设置了MapperProxyFactory,当Configuration.getMapper时,会调用MapperProxyFactory生成代理类MapperProxy,默认使用的是jdk动态代理。综上所述,当执行mapper的update方法时,会到MapperProxy代理方法invoke。后面会执行MapperMethod中执行execute方法。
会执行SqlSessionTemplate中的update方法。如下图当SqlSessionTemplate创建的时候,会设置SqlSessionTemplate的代理类sqlSessionProxy,SqlSessionInterceptor是代理类的拦截方法。执行SqlSessionTemplate中的update方法会进入SqlSessionInterceptor的invoke方法。

从if判断可以看到如果没有开启事务,sqlSession会提交事务。如果开启了,使用事务拦截器统一提交事务。
分析一下getSqlSession方法,主要是获取sqlSession,先进入getSqlSession方法看ransactionSynchronizationManager.getResource(sessionFactory);

从resources中获取以sessionFactory为key,值是defaultSqlSession的map,其中resources是ThreadLocal。第一次执行map是null。
请看源码,我添加了注释
java
public static SqlSession getSqlSession(SqlSessionFactory sessionFactory, ExecutorType executorType,
PersistenceExceptionTranslator exceptionTranslator) {
notNull(sessionFactory, NO_SQL_SESSION_FACTORY_SPECIFIED);
notNull(executorType, NO_EXECUTOR_TYPE_SPECIFIED);
//从ThreadLocal中获取SqlSessionHolder,可以通过SqlSessionHolder获取生成的sqlSession
SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory);
//从SqlSessionHolder中获取sqlSession,如果获取都会直接返回
SqlSession session = sessionHolder(executorType, holder);
if (session != null) {
return session;
}
//通过sessionFactory和执行器类型创建sqlSession
LOGGER.debug(() -> "Creating a new SqlSession");
session = sessionFactory.openSession(executorType);
//把创建好的sqlSession,放入到ThreadLocal中,只有开启事务才能放入
registerSessionHolder(sessionFactory, executorType, exceptionTranslator, session);
return session;
}
看一下openSession代码, tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
java
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level,
boolean autoCommit) {
Transaction tx = null;
try {
final Environment environment = configuration.getEnvironment();
final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
//1、创建了SpringManagedTransaction,传入数据源参数
tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
//2、创建执行器,传入了SpringManagedTransaction
final Executor executor = configuration.newExecutor(tx, execType);
//3、创建DefaultSqlSession,传入了执行器executor
return createSqlSession(configuration, executor, autoCommit);
} catch (Exception e) {
closeTransaction(tx); // may have fetched a connection so lets call close()
throw ExceptionFactory.wrapException("Error opening session. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
会创建Transaction类型的组件SpringManagedTransaction,传入了动态数据源。
java
public Transaction newTransaction(DataSource dataSource, TransactionIsolationLevel level, boolean autoCommit) {
return new SpringManagedTransaction(dataSource);
}
继续往下跟踪到registerSessionHolder(sessionFactory, executorType, exceptionTranslator, session);代码如下
1、里面代码判断synchronizations是不是null,这里上面提到过,在TranscationIntercetor中开启事务后,prepareSynchronization方法中设置了空集合,所以这里是TransactionSynchronizationManager.isSynchronizationActive()=true,synchronizations也是TreadLocal避免了线程不安全问题。

2、创建了SqlSessionHolder其中包含创建好的DefaultSqlSession。
3、往ThreadLocal中放入了Map,map的key是sessionFactory,value是2中创建的SqlSessionHolder。开启事务后,执行后面的sql可以从ThreadLocal取出。
4、创建了SqlSessionSynchronization其中包含创建好的sqlsession,放入到了集合中,此集合会放入1中synchronizations中,后面提交事务的时候会用到。
5、设置了SqlSessionHolder中SynchronizedWithTransaction=true。
总上所述,当开启事务后,在同一个事务中,使用mybatis执行多个sql时,会重复使用同一个DefaultSqlSession,(DefaultSqlSession被绑定到了线程中),也会使用同一个数据库连接,保证可以使用事务。如果没有开启事务,每次执行sql都会重新创建一个DefaultSqlSession。事务的开启和提交回滚都是mybatis来负责的。
继续回到MapperMethod.execute->sqlSession.update(command.getName(), param)
->executor.update(ms, wrapCollection(parameter));
->BaseExecutor.update
->SimpleExecutor.doUpdate
->prepareStatement(handler, ms.getStatementLog());
->Connection connection = getConnection(statementLog);-->openConnection();
-> this.connection = DataSourceUtils.getConnection(this.dataSource);
->doGetConnection 如下代码可以看到从ThreadLocal里面获取map使用动态数据源做key,获取数据库连接描述对象,这个数据库连接对象在开启事务后放入的,可以找找上面的内容有提到。SpringManagedTransaction类中
ConnectionHolder conHolder = (ConnectionHolder) TransactionSynchronizationManager.getResource(dataSource);
java
public static Connection doGetConnection(DataSource dataSource) throws SQLException {
Assert.notNull(dataSource, "No DataSource specified");
ConnectionHolder conHolder = (ConnectionHolder) TransactionSynchronizationManager.getResource(dataSource);
if (conHolder != null && (conHolder.hasConnection() || conHolder.isSynchronizedWithTransaction())) {
conHolder.requested();
if (!conHolder.hasConnection()) {
logger.debug("Fetching resumed JDBC Connection from DataSource");
conHolder.setConnection(fetchConnection(dataSource));
}
return conHolder.getConnection();
}
// Else we either got no holder or an empty thread-bound holder here.
logger.debug("Fetching JDBC Connection from DataSource");
Connection con = fetchConnection(dataSource);

获取数据库连接后会设置到SpringManagedTransaction类中的connection属性中,下面看一下SpringManagedTransaction类关系。
每次创建sqlsession的时候会先都会创建SpringManagedTransaction,SpringManagedTransaction当获取数据库连接后会设置到本类的属性connection上,创建执行器Excutor是会把SpringManagedTransaction设置进去,然后把Excutor设置到sqlsession中。这可以理解为同一个sqlsession对应同一个数据库连接java.sql.Connection。
(4)、事务提交后,当前线程ThreadLocal清理,sqlSession关闭
定位到TransactionAspectSupport.invokeWithinTransaction方法,方法内开启事务,执行拦截器和目标方法最后提交事务。下面看看提交事务方法。
commitTransactionAfterReturning(txInfo);
txInfo.getTransactionManager().commit(txInfo.getTransactionStatus())
->processCommit(defStatus);
->triggerBeforeCompletion
-> TransactionSynchronizationUtils.triggerBeforeCompletion();
遍历TransactionSynchronization执行beforeCompletion
java
for (TransactionSynchronization synchronization : TransactionSynchronizationManager.getSynchronizations()) {
try {
synchronization.beforeCompletion();
}
catch (Throwable tsex) {
logger.error("TransactionSynchronization.beforeCompletion threw exception", tsex);
}
}
java
public void beforeCompletion() {
// Issue #18 Close SqlSession and deregister it now
// because afterCompletion may be called from a different thread
if (!this.holder.isOpen()) {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Transaction synchronization deregistering SqlSession [" + this.holder.getSqlSession() + "]");
}
//删除ThreadLocal中绑定的sessionFactory,和sqlSession
TransactionSynchronizationManager.unbindResource(sessionFactory);
this.holderActive = false;
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Transaction synchronization closing SqlSession [" + this.holder.getSqlSession() + "]");
}
//关闭sqlSession
this.holder.getSqlSession().close();
}
}
主要看一下 TransactionSynchronizationManager.unbindResource(sessionFactory);清理绑定的sessionFactory和sqlSession的map集合
java
public static Object unbindResource(Object key) throws IllegalStateException {
Object actualKey = TransactionSynchronizationUtils.unwrapResourceIfNecessary(key);
Object value = doUnbindResource(actualKey);
if (value == null) {
throw new IllegalStateException(
"No value for key [" + actualKey + "] bound to thread [" + Thread.currentThread().getName() + "]");
}
return value;
}
java
private static Object doUnbindResource(Object actualKey) {
Map<Object, Object> map = resources.get();
if (map == null) {
return null;
}
Object value = map.remove(actualKey);
// Remove entire ThreadLocal if empty...
if (map.isEmpty()) {
resources.remove();
}
// Transparently suppress a ResourceHolder that was marked as void...
if (value instanceof ResourceHolder && ((ResourceHolder) value).isVoid()) {
value = null;
}
if (value != null && logger.isTraceEnabled()) {
logger.trace("Removed value [" + value + "] for key [" + actualKey + "] from thread [" +
Thread.currentThread().getName() + "]");
}
return value;
}
最终提交事务,方法在processCommit->triggerBeforeCommit->TransactionSynchronizationUtils.triggerBeforeCommit(status.isReadOnly())遍历所有的TransactionSynchronization 类型的组件前面添加过SqlSessionSynchronization其中包含了创建好的sqlsession
java
public static void triggerBeforeCommit(boolean readOnly) {
for (TransactionSynchronization synchronization : TransactionSynchronizationManager.getSynchronizations()) {
synchronization.beforeCommit(readOnly);
}
}
获取当前线程的sqlsession,执行提交操作。

执行到sqlsession中执行器的commit,设置不能自动提交。
继续执行执行器中的SpringManagedTransaction中的commit,SpringManagedTransaction在创建sqlsession的时候提到了,
最终获取SpringManagedTransaction中的connection,进行事务提交。
三、总结
1、TransactionInterceptor拦截到目标方法开启事务设置第一个ThreadLocal放入数据源为key,数据库连接描述类为value的map集合。
2、执行mybatis的sql时,sqlSession中的excutor中SpringManagedTransaction类会从第一个ThreadLocal中根据动态数据源取出相应的数据库连接执行sql,保证了开启的事务和执行sql同一个数据库连接。
3、在mybatis的sqlSessionTemplate执行增删改方法时,sqlSession的代理类SqlSession执行getSqlSession,如果开启了事务,出现第二个ThreadLocal,里面存放以sqlSessionFactory为key,defaultSqlSession为value的map集合,如何在同一个事务中,执行每个sql,defaultSqlSession会使用同一个。如果没有开启事务第二个ThreadLocal不生效,每次执行sql都会创建一次defaultSqlSession。事务的开启和提交都是mybatis控制的。
为何开启事务后,在事务中每个mapper增删改查操作都使用同一个sqlsession呢?因为 MyBatis 的 SqlSession 在设计上就是数据库连接(java.sql.Connection)的一个高级封装和门面(Facade)对象。一个 SqlSession 实例在其生命周期内,内部始终持有且仅持有一个 Connection 对象,同一个 SqlSession 就是同一个数据库连接,提交事务时,多个方法使用同一个SqlSession提交方法进而同一个数据库连接提交,保证了事务一致性
一个 SqlSession 实例 → 包含一个 Executor 实例 → 持有一个 Transaction 对象 → 管理一个唯一的 Connection 对象。
4、当事务提交成功或回滚时,会自动清理掉两个ThreadLocal中当前线程中的数据关闭SqlSession,回收 Connection 对象到线程池。