一、DefaultSqlSession的线程安全问题

从DefaultSqlSession类的注释可知,它不是一个线程安全的对象,通过DefaultSqlSessionFactory创建使用之后必须立即销毁,不能复用,只能局限于request或者方法的范围。而且在同一个Spring事务内的SqlSession只能有一个,所以Mybatis如果想集成到Spring就必须解决线程安全的问题!
二、SqlSessionTemplate
SqlSessionTemplate是Mybatis为了接入Spring提供的Bean,它通过TransactionSynchronizationManager中的ThreadLocal保存线程对应的SqlSession,实现SqlSession的线程安全!
SqlSessionTemplate通过sqlSessionProxy来执行CRUD,而sqlSessionProxy又是通过内部类SqlSessionInterceptor生成的代理,先看下SqlSessionInterceptor的类的继承关系:

由图可知,SqlSessionInterceptor是一个InvocationHandler,直接看invoke()源码:
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 获取sqlSession
SqlSession sqlSession = getSqlSession(
SqlSessionTemplate.this.sqlSessionFactory,
SqlSessionTemplate.this.executorType,
SqlSessionTemplate.this.exceptionTranslator);
try {
// 执行CRUD
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) {
// 关闭sqlSession,如果是事务方法则释放,不关闭
closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
}
}
}
SqlSessionUtils#getSqlSession()源码:
public static SqlSession getSqlSession(SqlSessionFactory sessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator) {
notNull(sessionFactory, NO_SQL_SESSION_FACTORY_SPECIFIED);
notNull(executorType, NO_EXECUTOR_TYPE_SPECIFIED);
// 获取SqlSessionHolder,从ThreadLocal获取
SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory);
SqlSession session = sessionHolder(executorType, holder);
if (session != null) {
return session;
}
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Creating a new SqlSession");
}
// 如果没拿到,则开启一个新会话并注册
session = sessionFactory.openSession(executorType);
registerSessionHolder(sessionFactory, executorType, exceptionTranslator, session);
return session;
}
由上述源码可以看到SqlSessionTemplate 通过ThreadLocal实现SqlSession的线程安全问题,并且管理了SqlSession的生命周期,不需要我们手动创建、提交或释放!
注意:
1、connection 和 session不是一个概念,一个connection连接可以有多次会话!此处没有释放或关闭connection
2、由上面源码可以看出,如果不是在事务方法中,则每次查询都会获取一个新的SqlSession,这样一级缓存就会失效了,而事务是SqlSession级别的(SqlSession的commit和释放),在一个事务方法中SqlSession被复用了,这样一级缓存才生效
另外,Mybatis自身也提供了一个保证SqlSession的线程安全的类-SqlSessionManager,但是由于SqlSessionTemplate 已存在,所以维护较少,可以自行了解下。
三、MapperFactoryBean
Mapper接口如果有实现类,应该会是什么样子呢?可以看看下面这个实现类例子:
public class UserDaoImpl implements UserDao {
private SqlSession sqlSession;
// 需要传入sqlSession
public void setSqlSession(SqlSession sqlSession) {
this.sqlSession = sqlSession;
}
public User getUser(String userId) {
// 这就是最原始的方式
return sqlSession.selectOne("org.mybatis.spring.sample.mapper.UserMapper.getUser", userId);
}
}
而MapperFactoryBean其实就是对Mapper接口和SqlSession进行封装的FactoryBean,看下这个xml配置:
<bean id="userMapper" class="org.mybatis.spring.mapper.MapperFactoryBean">
// 这个Mapper接口在后续通过MapperProxy创建动态代理会用到
<property name="mapperInterface" value="org.mybatis.spring.sample.mapper.UserMapper" />
// 也可以是SqlSessionTemplate
<property name="sqlSessionFactory" ref="sqlSessionFactory" />
</bean>
由上面的xml配置可以看到,userMapper的Classs类型是MapperFactoryBean,而这个就是注入到Spring IOC容器中的Class类型。通过xml方式一个个配置太麻烦,MapperScannerConfigurer是可以进行包扫描配置,后续的文章再详细讲解。
本文探讨了DefaultSqlSession的线程不安全问题,重点介绍了SqlSessionTemplate如何通过ThreadLocal实现线程安全,并讲解了MapperFactoryBean在整合过程中的角色。同时提到了SqlSessionManager的补充作用。
2286

被折叠的 条评论
为什么被折叠?



