DefaultSqlSession是線程不安全的
在Mybatis中SqlSession是提供給外部調(diào)用的頂層接口局骤,實(shí)現(xiàn)類有:DefaultSqlSession鹃愤、SqlSessionManager以及mybatis-spring提供的實(shí)現(xiàn)SqlSessionTemplate。默認(rèn)實(shí)現(xiàn)類為DefaultSqlSession如绸,是線程不完全的嘱朽。類結(jié)構(gòu)圖如下:
對于Mybatis提供的原生實(shí)現(xiàn)類來說,用的最多就是DefaultSqlSession怔接,但是我們知道DefaultSqlSession這個類不是線程安全的搪泳!如下:
SqlSessionTemplate是如何保證線程安全的
在我們平時的開發(fā)中通常會用到Spring,也會用到mybatis-spring框架扼脐,在Spring集成Mybatis的時候我們可以用到SqlSessionTemplate(Spring提供的SqlSession實(shí)現(xiàn)類)案训,使用場景案例如下:
查看SqlSessionTemplate的源碼注釋如下:
通過源碼注釋可以看到SqlSessionTemplate是線程安全的類布轿,并且實(shí)現(xiàn)了SqlSession接口球凰,也就是說我們可以通過SqlSessionTemplate來代替以往的DefaultSqlSession完成對數(shù)據(jù)庫CRUD操作虐沥,并且還保證單例線程安全,那么它是如何保證線程安全的呢脏榆?
首先猖毫,通過SqlSessionTemplate擁有的三個重載的構(gòu)造方法分析,最終都會調(diào)用最后一個構(gòu)造方法须喂,會初始化一個SqlSessionProxy的代理對象吁断,如果調(diào)用代理類實(shí)例中實(shí)現(xiàn)的SqlSession接口中定義的方法,該調(diào)用會被導(dǎo)向SqlSessionInterceptor的invoke方法觸發(fā)代理邏輯
接下來查看SqlSessionInterceptor的invoke方法
- 通過getSqlSession方法獲取SqlSession對象(如果使用了事務(wù)坞生,從Spring事務(wù)上下文獲茸幸邸)
- 調(diào)用SqlSession的接口方法操作數(shù)據(jù)庫獲取結(jié)果
- 返回結(jié)果集
- 若發(fā)生異常則轉(zhuǎn)換后拋出異常,并最終關(guān)閉SqlSession對象
private class SqlSessionInterceptor implements InvocationHandler {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//獲取SqlSession(這個sqlSession才是真正使用的是己,它不是線程安全的)
//這個方法可以根據(jù)Spring的事務(wù)上下文來獲取事務(wù)范圍內(nèi)的SqlSession
SqlSession sqlSession = getSqlSession(SqlSessionTemplate.this.sqlSessionFactory,
SqlSessionTemplate.this.executorType, SqlSessionTemplate.this.exceptionTranslator);
try {
//調(diào)用sqlSession對象的方法(select又兵、update等)
Object result = method.invoke(sqlSession, args);
//判斷是否為事務(wù)操作,如果未被Spring事務(wù)托管則自動提交commit
if (!isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {
sqlSession.commit(true);
}
return result;
} catch (Throwable t) {
//如果出現(xiàn)異常則根據(jù)情況轉(zhuǎn)換后拋出
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 {
//最終關(guān)閉sqlSession對象
if (sqlSession != null) {
closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
}
}
}
}
重點(diǎn)分析getSqlSession方法如下:
- 若無法從當(dāng)前線程的ThreadLocal中獲取則通過SqlSessionFactory獲取SqlSession
- 若開啟了事務(wù),則從當(dāng)前線程的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);
//若開啟了事務(wù)支持沛厨,則從當(dāng)前的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);
//若為事務(wù)操作宙地,則注冊SqlSessionHolder到ThrealLocal中
registerSessionHolder(sessionFactory, executorType, exceptionTranslator, session);
return session;
}
大致的分析到此為止,文中只對主要的過程進(jìn)行了大致的說明逆皮,小伙伴若想要仔細(xì)分析宅粥,可以自己打開源碼走一遍!
SqlSessionManger又是什么电谣?
SqlSessionManager是Mybatis提供的線程安全的操作類秽梅,且看定義如下:
通過上圖可以發(fā)現(xiàn)SqlSessionManager的構(gòu)造方法竟然是private的,那我們怎么創(chuàng)建對象呢剿牺?其實(shí)SqlSessionManager創(chuàng)建對象是通過newInstance方法創(chuàng)建對象的风纠,但需要注入它雖然是私有的構(gòu)造方法,并且提供給我們一個公有的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方法及其重載方法是直接通過調(diào)用底層封裝SqlSessionFactory對象的openSession方法來創(chuàng)建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中實(shí)現(xiàn)SqlSession接口中的方法,例如:select懂酱、update等竹习,都是直接調(diào)用SqlSessionProxy代理對象中相應(yīng)的方法,在創(chuàng)建該代理對像的時候使用的InvocationHandler對象是SqlSessionInterceptor列牺,他是定義在SqlSessionManager的一個內(nèi)部類整陌,其定義如下:
private class SqlSessionInterceptor implements InvocationHandler {
public SqlSessionInterceptor() {
// Prevent Synthetic Access
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//獲取當(dāng)前ThreadLocal上下文的SqlSession
final SqlSession sqlSession = SqlSessionManager.this.localSqlSession.get();
if (sqlSession != null) {
try {
//從上下文獲取到SqlSession之后調(diào)用對應(yīng)的方法
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對象在什么時候賦值對應(yīng)的SqlSession,往上查找最終定位代碼(若調(diào)用startManagerSession方法將設(shè)置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的聯(lián)系與區(qū)別
- SqlSessionTemplate是Mybatis為了接入Spring提供的Bean泌辫。通過TransactionSynchronizationManager中的ThreadLocal<Map<Object, Object>>保存線程對應(yīng)的SqlSession,實(shí)現(xiàn)session的線程安全九默。
- SqlSessionManager是Mybatis不接入Spring時用于管理SqlSession的Bean震放。通過SqlSessionManagger的ThreadLocal<SqlSession>實(shí)現(xiàn)session的線程安全。
總結(jié)分析
通過上面的代碼分析驼修,我們可以看出Spring解決SqlSession線程安全問題的思路就是動態(tài)代理與ThreadLocal的運(yùn)用殿遂,我們可以觸類旁通:當(dāng)遇到線程不安全的類,但是又想當(dāng)作線程安全的類使用乙各,則可以使用ThreadLocal進(jìn)行線程上下文的隔離墨礁,此處的動態(tài)代理技術(shù)更好的解決了上層API調(diào)用的非侵入性,保證API接口調(diào)用的高內(nèi)聚耳峦、低耦合原則
本文由博客一文多發(fā)平臺 OpenWrite 發(fā)布恩静!