MyBatis(六)SqlSessionTemplate是如何保证线程安全的
前面說到DefaultSqlSession不是線程安全的,所以在MyBatis和spring項(xiàng)目整合的時(shí)候不能直接使用DefaultSqlSession,而是自己封裝了一個(gè)線程安全的SqlSessionTemplate,通過spring管理的SqlSessionTemplate來創(chuàng)建SqlSession,而SqlSessionTemplate又是單例的,那么它是怎么保證線程安全的呢?
SqlSessionTemplate
最容易想到的就是ThreadLocal,在每個(gè)線程保存各自的SqlSession,這樣執(zhí)行commit,close操作就不需要擔(dān)心線程安全的問題了。
那么就看看spring里到底是怎么實(shí)現(xiàn)的吧
這里的核心處理就在增強(qiáng)的SqlSessionInterceptor的invoke方法里
private class SqlSessionInterceptor implements InvocationHandler {private SqlSessionInterceptor() {}public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { //通過一個(gè)工具類獲取SqlSession(參數(shù)有SqlSessionFactory,Executor類型等)SqlSession sqlSession = SqlSessionUtils.getSqlSession(SqlSessionTemplate.this.sqlSessionFactory, SqlSessionTemplate.this.executorType, SqlSessionTemplate.this.exceptionTranslator);Object unwrapped;try {Object result = method.invoke(sqlSession, args);//判斷當(dāng)前SqlSession是否是Spring管理的,如果不是,就直接調(diào)用SqlSession的commit方法if (!SqlSessionUtils.isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {sqlSession.commit(true);}unwrapped = result;} catch (Throwable var11) {unwrapped = ExceptionUtil.unwrapThrowable(var11);if (SqlSessionTemplate.this.exceptionTranslator != null && unwrapped instanceof PersistenceException) {//如果出現(xiàn)異常,則調(diào)用SqlSessionUtils的closeSqlSession方法,這里執(zhí)行后finally就不會(huì)再執(zhí)行closeSqlSession了 因?yàn)閟qlSession 被置為null了SqlSessionUtils.closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);sqlSession = null;Throwable translated = SqlSessionTemplate.this.exceptionTranslator.translateExceptionIfPossible((PersistenceException)unwrapped);if (translated != null) {unwrapped = translated;}}throw (Throwable)unwrapped;} finally {if (sqlSession != null) {//最后都會(huì)調(diào)用一次closeSqlSession來釋放資源SqlSessionUtils.closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);}}return unwrapped;}}不難看出,保證線程安全的關(guān)鍵代碼在SqlSessionUtils里,最重要的是其中的getSqlSession方法
public static SqlSession getSqlSession(SqlSessionFactory sessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator) {Assert.notNull(sessionFactory, "No SqlSessionFactory specified");Assert.notNull(executorType, "No ExecutorType specified");//根據(jù)SqlSessionFactory從TransactionSynchronizationManager中獲取SqlSessionHolder,這里的SqlSession就是從TransactionSynchronizationManager的ThreadlLocal以sqlSessionFactory為key獲取到的SqlSessionHolder holder = (SqlSessionHolder)TransactionSynchronizationManager.getResource(sessionFactory);//通過holder獲取SqlSession,并且將其引用計(jì)數(shù)referenceCount + 1 SqlSession session = sessionHolder(executorType, holder);if (session != null) {return session;} else {if (LOGGER.isDebugEnabled()) {LOGGER.debug("Creating a new SqlSession");}//如果holder里沒有SqlSession,那么就通過SqlSessionFactory創(chuàng)建一個(gè)新的DefaultSqlSessionsession = sessionFactory.openSession(executorType);//注冊(cè)生成的SqlSessionregisterSessionHolder(sessionFactory, executorType, exceptionTranslator, session);return session;}}---- TransactionSynchronizationManager private static final ThreadLocal<Map<Object, Object>> resources = new NamedThreadLocal("Transactional resources");public static Object getResource(Object key) {//根據(jù)sqlSessionFactory獲取一個(gè)實(shí)際的key--如果是代理對(duì)象,actual就是目標(biāo)Key對(duì)象Object actualKey = TransactionSynchronizationUtils.unwrapResourceIfNecessary(key);Object value = doGetResource(actualKey);if (value != null && logger.isTraceEnabled()) {logger.trace("Retrieved value [" + value + "] for key [" + actualKey + "] bound to thread [" + Thread.currentThread().getName() + "]");}return value;}private static Object doGetResource(Object actualKey) {//從ThreadLocal里獲取當(dāng)前線程的map,然后從map里以sqlSessionFactory為key獲取對(duì)應(yīng)的value也就是SqlSessionHolderMap<Object, Object> map = (Map)resources.get();if (map == null) {return null;} else {Object value = map.get(actualKey);if (value instanceof ResourceHolder && ((ResourceHolder)value).isVoid()) {map.remove(actualKey);if (map.isEmpty()) {resources.remove();}value = null;}return value;}}從上面的源碼不難猜出:
registerSessionHolder的方法其實(shí)就是將SqlSessionFactory和SqlSessionHolder以key-value的方式存儲(chǔ)在TransactionSynchronizationManager 的ThreadLocal里,這樣我們才能夠從ThreadLocal里獲取到對(duì)應(yīng)的SqlSessionHolder
closeSqlSession方法如下:先從TransactionSynchronizationManager中獲取holder,然后判斷當(dāng)前SqlSession是否是從holder獲取的,如果是說明session由spring管理的,那么直接調(diào)用holder.released(),將其引用計(jì)數(shù)-1(這跟重入鎖的涉及有點(diǎn)像);如果不是則直接關(guān)閉session
public static void closeSqlSession(SqlSession session, SqlSessionFactory sessionFactory) {Assert.notNull(session, "No SqlSession specified");Assert.notNull(sessionFactory, "No SqlSessionFactory specified");SqlSessionHolder holder = (SqlSessionHolder)TransactionSynchronizationManager.getResource(sessionFactory);if (holder != null && holder.getSqlSession() == session) {if (LOGGER.isDebugEnabled()) {LOGGER.debug("Releasing transactional SqlSession [" + session + "]");}//spring管理,則使用holder進(jìn)行釋放,這里只是減少了一下引用計(jì)數(shù),這樣后面在本線程中可以復(fù)用holder.released();} else {if (LOGGER.isDebugEnabled()) {LOGGER.debug("Closing non transactional SqlSession [" + session + "]");}session.close();}}//判斷session是否由spring管理public static boolean isSqlSessionTransactional(SqlSession session, SqlSessionFactory sessionFactory) {Assert.notNull(session, "No SqlSession specified");Assert.notNull(sessionFactory, "No SqlSessionFactory specified");SqlSessionHolder holder = (SqlSessionHolder)TransactionSynchronizationManager.getResource(sessionFactory);return holder != null && holder.getSqlSession() == session;}?
SqlSessionManager
SqlSession除了默認(rèn)的實(shí)現(xiàn)類DefaultSqlSession,還有一個(gè)線程安全的實(shí)現(xiàn)SqlSessionManager
從其源碼不難看出,其線程安全也是通過ThreadLocal實(shí)現(xiàn)的,SqlSessionManager中存儲(chǔ)了SqlSessionFactory,通過其創(chuàng)建SqlSession的代理對(duì)象
//SqlSessionManager提供了很多重載的newInstance方法,通過SqlSessionFactoryBuilder會(huì)話工廠 private final SqlSessionFactory sqlSessionFactory; private final SqlSession sqlSessionProxy; //用于保存與當(dāng)前線程綁定的SqlSession private final ThreadLocal<SqlSession> localSqlSession = new ThreadLocal<SqlSession>();private SqlSessionManager(SqlSessionFactory sqlSessionFactory) {this.sqlSessionFactory = sqlSessionFactory;//通過jdk動(dòng)態(tài)代理創(chuàng)建代理SqlSessionthis.sqlSessionProxy = (SqlSession) Proxy.newProxyInstance(SqlSessionFactory.class.getClassLoader(),new Class[]{SqlSession.class},new SqlSessionInterceptor());}public void startManagedSession() {//綁定當(dāng)前線程創(chuàng)建的SqlSession到ThreadLocal里this.localSqlSession.set(openSession());}@Overridepublic SqlSession openSession() {return sqlSessionFactory.openSession();}private class SqlSessionInterceptor implements InvocationHandler {public SqlSessionInterceptor() {// Prevent Synthetic Access}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {//獲取當(dāng)前線程綁定的SqlSessionfinal SqlSession sqlSession = SqlSessionManager.this.localSqlSession.get();if (sqlSession != null) {try {//執(zhí)行數(shù)據(jù)庫操作return method.invoke(sqlSession, args);} catch (Throwable t) {throw ExceptionUtil.unwrapThrowable(t);}} else {//如果localSqlSessio中沒有SqlSession,即當(dāng)前線程中沒有創(chuàng)建SqlSession,則調(diào)用openSession(這里直接使用SqlSessionFactory來創(chuàng)建SqlSession)final SqlSession autoSqlSession = openSession();try {//使用新創(chuàng)建的SqlSession執(zhí)行數(shù)據(jù)庫操作final Object result = method.invoke(autoSqlSession, args);autoSqlSession.commit();return result;} catch (Throwable t) {autoSqlSession.rollback();throw ExceptionUtil.unwrapThrowable(t);} finally {autoSqlSession.close();}}}}SqlSessionManager在使用的時(shí)候需要先調(diào)用startManagedSession來創(chuàng)建會(huì)話并存入ThreadLocal,如果沒有開啟的話那就直接使用SqlSessionFactory創(chuàng)建的SqlSession(此時(shí)會(huì)話不放入ThreadLocal,就無法保證線程安全餓了)
SqlSessionManager還實(shí)現(xiàn)了SqlSession的接口方法,比如:insert,update,select,會(huì)直接調(diào)用sqlSessionProxy代理對(duì)象中相應(yīng)的方法。實(shí)際執(zhí)行就會(huì)執(zhí)行SqlSessionInterceptor里的invoke方法
總結(jié):
-
DefaultSqlSession 不是線程安全的,不要使用單例模式
-
SqlSessionTemplate 和?SqlSessionManager 都持有 SqlSessionFactory,并且都是線程安全的,都通過ThreadLocal來實(shí)現(xiàn)SqlSession的生命周期管理
-
SqlSessionTemplate 由 Spring 事務(wù)管理器決定是否共用 SqlSession。事務(wù)內(nèi)部共用 SqlSession,非事務(wù)不共用 SqlSession。SqlSessionManager 由使用者決定是否共用 SqlSession
總結(jié)
以上是生活随笔為你收集整理的MyBatis(六)SqlSessionTemplate是如何保证线程安全的的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: MyBatis(一)MyBatis介绍和
- 下一篇: MyBatis(三)MyBatis缓存和