DefaultSqlSession是线程不安全的
在Mybatis中SqlSession是提供给外部调用的顶层接口,实现类有:DefaultSqlSession、SqlSessionManager以及mybatis-spring提供的实现SqlSessionTemplate。默认实现类为DefaultSqlSession,是线程不完全的。类结构图如下:
对于Mybatis提供的原生实现类来说,用的最多就是DefaultSqlSession,但是我们知道DefaultSqlSession这个类不是线程安全的!如下:
SqlSessionTemplate是如何保证线程安全的
在我们平时的开发中通常会用到Spring,也会用到mybatis-spring框架,在Spring集成Mybatis的时候我们可以用到SqlSessionTemplate(Spring提供的SqlSession实现类),使用场景案例如下:
查看SqlSessionTemplate的源码注释如下:
通过源码注释可以看到SqlSessionTemplate是线程安全的类,并且实现了SqlSession接口,也就是说我们可以通过SqlSessionTemplate来代替以往的DefaultSqlSession完成对数据库CRUD操作,并且还保证单例线程安全,那么它是如何保证线程安全的呢?
首先,通过SqlSessionTemplate拥有的三个重载的构造方法分析,最终都会调用最后一个构造方法,会初始化一个SqlSessionProxy的代理对象,如果调用代理类实例中实现的SqlSession接口中定义的方法,该调用会被导向SqlSessionInterceptor的invoke方法触发代理逻辑
接下来查看SqlSessionInterceptor的invoke方法
- 通过getSqlSession方法获取SqlSession对象(如果使用了事务,从Spring事务上下文获取)
- 调用SqlSession的接口方法操作数据库获取结果
- 返回结果集
- 若发生异常则转换后抛出异常,并最终关闭SqlSession对象
private class SqlSessionInterceptor implements InvocationHandler {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//获取SqlSession(这个sqlSession才是真正使用的,它不是线程安全的)
//这个方法可以根据Spring的事务上下文来获取事务范围内的SqlSession
SqlSession sqlSession = getSqlSession(SqlSessionTemplate.this.sqlSessionFactory,
SqlSessionTemplate.this.executorType, SqlSessionTemplate.this.exceptionTranslator);
try {
//调用sqlSession对象的方法(select、update等)
Object result = method.invoke(sqlSession, args);
//判断是否为事务操作,如果未被Spring事务托管则自动提交commit
if (!isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {
sqlSession.commit(true);
}
return result;
} catch (Throwable t) {
//如果出现异常则根据情况转换后抛出
Throwable unwrapped = unwrapThrowable(t);
if (SqlSessionTemplate.this.exceptionTranslator != null && unwrapped instanceof PersistenceException) {
closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
sqlSession = null;
Throwable translated = SqlSessionTemplate.this.exceptionTranslator
.translateExceptionIfPossible((PersistenceException) unwrapped);
if (translated != null) {
unwrapped = translated;
}
}
throw unwrapped;
} finally {
//最终关闭sqlSession对象
if (sqlSession != null) {
closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
}
}
}
}
重点分析getSqlSession方法如下:
- 若无法从当前线程的ThreadLocal中获取则通过SqlSessionFactory获取SqlSession
- 若开启了事务,则从当前线程的ThrealLocal上下文中获取SqlSessionHolder
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);
SqlSession session = sessionHolder(executorType, holder);
if (session != null) {
return session;
}
LOGGER.debug(() -> "Creating a new SqlSession");
//若无法从ThrealLocal上下文中获取则通过SqlSessionFactory获取SqlSession
session = sessionFactory.openSession(executorType);
//若为事务操作,则注册SqlSessionHolder到ThrealLocal中
registerSessionHolder(sessionFactory, executorType, exceptionTranslator, session);
return session;
}
大致的分析到此为止,文中只对主要的过程进行了大致的说明,小伙伴若想要仔细分析,可以自己打开源码走一遍!
SqlSessionManger又是什么?
SqlSessionManager是Mybatis提供的线程安全的操作类,且看定义如下:
通过上图可以发现SqlSessionManager的构造方法竟然是private的,那我们怎么创建对象呢?其实SqlSessionManager创建对象是通过newInstance方法创建对象的,但需要注入它虽然是私有的构造方法,并且提供给我们一个公有的newInstance方法,但它并不是一个单例模式!
newInstance有很多重载方法,如下所示:
public static SqlSessionManager newInstance(Reader reader) {
return new SqlSessionManager(new SqlSessionFactoryBuilder().build(reader, null, null));
}
public static SqlSessionManager newInstance(Reader reader, String environment) {
return new SqlSessionManager(new SqlSessionFactoryBuilder().build(reader, environment, null));
}
public static SqlSessionManager newInstance(Reader reader, Properties properties) {
return new SqlSessionManager(new SqlSessionFactoryBuilder().build(reader, null, properties));
}
public static SqlSessionManager newInstance(InputStream inputStream) {
return new SqlSessionManager(new SqlSessionFactoryBuilder().build(inputStream, null, null));
}
public static SqlSessionManager newInstance(InputStream inputStream, String environment) {
return new SqlSessionManager(new SqlSessionFactoryBuilder().build(inputStream, environment, null));
}
public static SqlSessionManager newInstance(InputStream inputStream, Properties properties) {
return new SqlSessionManager(new SqlSessionFactoryBuilder().build(inputStream, null, properties));
}
public static SqlSessionManager newInstance(SqlSessionFactory sqlSessionFactory) {
return new SqlSessionManager(sqlSessionFactory);
}
SqlSessionManager的openSession方法及其重载方法是直接通过调用底层封装SqlSessionFactory对象的openSession方法来创建SqlSession对象的,如下所示:
@Override
public SqlSession openSession(boolean autoCommit) {
return sqlSessionFactory.openSession(autoCommit);
}
@Override
public SqlSession openSession(Connection connection) {
return sqlSessionFactory.openSession(connection);
}
@Override
public SqlSession openSession(TransactionIsolationLevel level) {
return sqlSessionFactory.openSession(level);
}
@Override
public SqlSession openSession(ExecutorType execType) {
return sqlSessionFactory.openSession(execType);
}
@Override
public SqlSession openSession(ExecutorType execType, boolean autoCommit) {
return sqlSessionFactory.openSession(execType, autoCommit);
}
@Override
public SqlSession openSession(ExecutorType execType, TransactionIsolationLevel level) {
return sqlSessionFactory.openSession(execType, level);
}
@Override
public SqlSession openSession(ExecutorType execType, Connection connection) {
return sqlSessionFactory.openSession(execType, connection);
}
SqlSessionManager中实现SqlSession接口中的方法,例如:select、update等,都是直接调用SqlSessionProxy代理对象中相应的方法,在创建该代理对像的时候使用的InvocationHandler对象是SqlSessionInterceptor,他是定义在SqlSessionManager的一个内部类,其定义如下:
private class SqlSessionInterceptor implements InvocationHandler {
public SqlSessionInterceptor() {
// Prevent Synthetic Access
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//获取当前ThreadLocal上下文的SqlSession
final SqlSession sqlSession = SqlSessionManager.this.localSqlSession.get();
if (sqlSession != null) {
try {
//从上下文获取到SqlSession之后调用对应的方法
return method.invoke(sqlSession, args);
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
} else {
//如果无法从ThreadLocal上下文中获取SqlSession则新建一个SqlSession
try (SqlSession autoSqlSession = openSession()) {
try {
final Object result = method.invoke(autoSqlSession, args);
autoSqlSession.commit();
return result;
} catch (Throwable t) {
autoSqlSession.rollback();
throw ExceptionUtil.unwrapThrowable(t);
}
}
}
}
}
此处我们在思考下ThreadLocal的localSqlSession对象在什么时候赋值对应的SqlSession,往上查找最终定位代码(若调用startManagerSession方法将设置ThreadLocal的localSqlSession上下文中的SqlSession对象),如下所示:
public void startManagedSession() {
this.localSqlSession.set(openSession());
}
public void startManagedSession(boolean autoCommit) {
this.localSqlSession.set(openSession(autoCommit));
}
public void startManagedSession(Connection connection) {
this.localSqlSession.set(openSession(connection));
}
public void startManagedSession(TransactionIsolationLevel level) {
this.localSqlSession.set(openSession(level));
}
public void startManagedSession(ExecutorType execType) {
this.localSqlSession.set(openSession(execType));
}
public void startManagedSession(ExecutorType execType, boolean autoCommit) {
this.localSqlSession.set(openSession(execType, autoCommit));
}
public void startManagedSession(ExecutorType execType, TransactionIsolationLevel level) {
this.localSqlSession.set(openSession(execType, level));
}
public void startManagedSession(ExecutorType execType, Connection connection) {
this.localSqlSession.set(openSession(execType, connection));
}
SqlSessionTemplate与SqlSessionManager的联系与区别
- SqlSessionTemplate是Mybatis为了接入Spring提供的Bean。通过TransactionSynchronizationManager中的ThreadLocal
- SqlSessionManager是Mybatis不接入Spring时用于管理SqlSession的Bean。通过SqlSessionManagger的ThreadLocal
实现session的线程安全。
总结分析
通过上面的代码分析,我们可以看出Spring解决SqlSession线程安全问题的思路就是动态代理与ThreadLocal的运用,我们可以触类旁通:当遇到线程不安全的类,但是又想当作线程安全的类使用,则可以使用ThreadLocal进行线程上下文的隔离,此处的动态代理技术更好的解决了上层API调用的非侵入性,保证API接口调用的高内聚、低耦合原则
本文由博客一文多发平台 OpenWrite 发布!