之前看网上都说,mybatis自带的DefaultSqlSession是线程不安全的,但是Spring-Mybatis整合之后,融入到框架中,SqlSession就变成线程安全的。但是对如何实现线程安全的,一直是不清楚,所以看源码吧。
首先先查看spring-mybatis的官方说明文档,地址:http://www.mybatis.org/spring/zh/,其中需要着重关注【第六章:注入映射器,MapperFactoryBean】。官方给的主要配置如下
<bean id="userMapper" class="org.mybatis.spring.mapper.MapperFactoryBean">
<property name="mapperInterface" value="org.mybatis.spring.sample.mapper.UserMapper" />
<property name="sqlSessionFactory" ref="sqlSessionFactory" />
</bean>
然后查看MapperFactoryBean的声明
public class MapperFactoryBean<T> extends SqlSessionDaoSupport implements FactoryBean<T>
该类是一个泛型类,可以理解为泛型T是这个类需要代理生成的对象类型。
下面继续关注配置中的两个成员变量,mapperInterface和sqlSessionFactory。mapperInterface是泛型T的具体类型,作用就如同类声明里面说的,接下来最重要的是sqlSessionFactory,下面看一下sqlSessionFactory的setter方法
public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) {
if (this.sqlSessionTemplate == null || sqlSessionFactory != this.sqlSessionTemplate.getSqlSessionFactory()) {
this.sqlSessionTemplate = createSqlSessionTemplate(sqlSessionFactory);
}
}
可以看到虽然配置的是SqlSessionFactory,但是最终作用的还是SqlSessionTemplate这个模板类,下钻到createSqlSessionTemplate方法,最后发现是SqlSessionTemplate的构造方法实现
public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator) { notNull(sqlSessionFactory, "Property 'sqlSessionFactory' is required"); notNull(executorType, "Property 'executorType' is required"); this.sqlSessionFactory = sqlSessionFactory; this.executorType = executorType; this.exceptionTranslator = exceptionTranslator; this.sqlSessionProxy = (SqlSession) newProxyInstance( SqlSessionFactory.class.getClassLoader(), new Class[] { SqlSession.class }, new SqlSessionInterceptor()); }
看到这,是否觉得有一行代码很熟悉?没错,就是下面这个基于jdk的动态代理
this.sqlSessionProxy = (SqlSession) newProxyInstance( SqlSessionFactory.class.getClassLoader(), new Class[] { SqlSession.class }, new SqlSessionInterceptor());
了解动态代理的同学应该明白,这个代理对象的关键拦截部分在于InvocationHandler的实现类,也就是这里的SqlSessionInterceptor类,而实现拦截的方法就在于InvocationHandler#invoke中
SqlSession sqlSession = getSqlSession(
SqlSessionTemplate.this.sqlSessionFactory,
SqlSessionTemplate.this.executorType,
SqlSessionTemplate.this.exceptionTranslator);//此时获取的是mybatis生成的DefaultSqlSession
try {
Object result = method.invoke(sqlSession, args);
if (!isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {
// force commit even on non-dirty sessions because some databases require
// a commit/rollback before calling close()
sqlSession.commit(true);
}
return result;
} catch (Throwable t) {
Throwable unwrapped = unwrapThrowable(t);
if (SqlSessionTemplate.this.exceptionTranslator != null && unwrapped instanceof PersistenceException) {
// release the connection to avoid a deadlock if the translator is no loaded. See issue #22
closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
sqlSession = null;
Throwable translated = SqlSessionTemplate.this.exceptionTranslator.translateExceptionIfPossible((PersistenceException) unwrapped);
if (translated != null) {
unwrapped = translated;
}
}
throw unwrapped;
} finally {
if (sqlSession != null) {
closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
}
}
从其中我们可以看到,sqlsession的获取来自于getSqlSession方法,进入该方法分为两块主要内容,一个是通过SqlSessionFactory获取绑定的SqlSessionHolder(SqlSession的封装对象,参看类定义),如果SqlSessionHolder为空的话,就调用mybatis的接口生成一个SqlSession,然后将其封装为SqlSessionHolder对象,然后进行注册,那么线程安全的关键就在于这个注册了,所以查看registerSessionHolder方法
private static void registerSessionHolder(SqlSessionFactory sessionFactory, ExecutorType executorType,
PersistenceExceptionTranslator exceptionTranslator, SqlSession session) {
SqlSessionHolder holder;
if (TransactionSynchronizationManager.isSynchronizationActive()) {
Environment environment = sessionFactory.getConfiguration().getEnvironment();
if (environment.getTransactionFactory() instanceof SpringManagedTransactionFactory) {
LOGGER.debug(() -> "Registering transaction synchronization for SqlSession [" + session + "]");
holder = new SqlSessionHolder(session, executorType, exceptionTranslator);//将sqlSession进行封装
TransactionSynchronizationManager.bindResource(sessionFactory, holder);//内部数据结构是ThreadLocal<Map<factory,resource>>,将该Map与当前线程进行绑定
TransactionSynchronizationManager.registerSynchronization(new SqlSessionSynchronization(holder, sessionFactory));
holder.setSynchronizedWithTransaction(true);
holder.requested();//当前线程的SqlSession的内部计数器+1
} else {
if (TransactionSynchronizationManager.getResource(environment.getDataSource()) == null) {
LOGGER.debug(() -> "SqlSession [" + session + "] was not registered for synchronization because DataSource is not transactional");
} else {
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");
}
}
从代码的逻辑中可以看到,SqlSession的线程安全依赖于事务管理器TransactionSynchronizationManager,而结合代码中关键的一行
TransactionSynchronizationManager.bindResource(sessionFactory, holder);//内部数据结构是ThreadLocal<Map<factory,resource>>,将该Map与当前线程进行绑定
可以大致猜测到,如果当前线程中的SqlSession(实际上是被包装为SqlSessionHolder)未与SqlSessionFactory绑定,那么就使用Map<SqlSessionFactory,SqlSessionHolder>进行绑定关系,然后通过bindResource方法,存到本地线程变量中,代码如下
public static void bindResource(Object key, Object value) throws IllegalStateException {
Object actualKey = TransactionSynchronizationUtils.unwrapResourceIfNecessary(key);
Assert.notNull(value, "Value must not be null");
Map<Object, Object> map = resources.get();//resource-->ThreadLocal<Map<SqlSessionFactory,SqlSessionHolder>>
// set ThreadLocal Map if none found
if (map == null) {
map = new HashMap<>();
resources.set(map);
}
Object oldValue = map.put(actualKey, value);
// Transparently suppress a ResourceHolder that was marked as void...
if (oldValue instanceof ResourceHolder && ((ResourceHolder) oldValue).isVoid()) {
oldValue = null;
}
if (oldValue != null) {
throw new IllegalStateException("Already value [" + oldValue + "] for key [" +
actualKey + "] bound to thread [" + Thread.currentThread().getName() + "]");
}
if (logger.isTraceEnabled()) {
logger.trace("Bound value [" + value + "] for key [" + actualKey + "] to thread [" +
Thread.currentThread().getName() + "]");
}
}
该方法的大致意思就是从当前线程的本地变量ThreadLocal中取出Map,如果不存在的话就将key和value塞入Map
所以方法走到现在,其实spring-mybatis如何实现线程安全的思路已经清晰了,通过ThreadLocal,确切的说是通过对象ThreadLocal<Map<SqlSessionFactory,SqlSessionHolder>>。
大致理一下流程:web容器接受请求,分配一个线程用于处理该请求(该线程中有能处理的请求的对象Controller/Service/DAO),最终操作数据库是通过DAO对象来的,而DAO对象是如何生成,在开篇已经介绍了,通过MapperFactoryBean包装生成,而这个DAO对象的数据库会话其实就是来自于SqlSessionFactory.getSqlSession方法,也就是mybatis的API获取,通过TransactionSynchronizedManager类以及其成员变量ThreadLocal实现将获取的SqlSession与SqlSessionFactory绑定并且关联到当前线程。
其实还有一点一直不是很清楚,到底什么是线程安全?