Mybatis SqlSessionTemplate 源碼解析web
在使用Mybatis與Spring集成的時候咱們用到了SqlSessionTemplate 這個類。spring
<bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate"> <constructor-arg index="0" ref="sqlSessionFactory" /> </bean>
經過源碼咱們何以看到 SqlSessionTemplate 實現了SqlSession接口,也就是說咱們可使用SqlSessionTemplate 來代理以往的DefailtSqlSession完成對數據庫的操做,可是DefailtSqlSession這個類不是線程安全的,因此這個類不能夠被設置成單例模式的。sql
若是是常規開發模式 咱們每次在使用DefailtSqlSession的時候都從SqlSessionFactory當中獲取一個就能夠了。可是與Spring集成之後,Spring提供了一個全局惟一的SqlSessionTemplate示例 來完成DefailtSqlSession的功能,問題就是:不管是多個dao使用一個SqlSessionTemplate,仍是一個dao使用一個SqlSessionTemplate,SqlSessionTemplate都是對應一個sqlSession,當多個web線程調用同一個dao時,它們使用的是同一個SqlSessionTemplate,也就是同一個SqlSession,那麼它是如何確保線程安全的呢?讓咱們一塊兒來分析一下。數據庫
(1)首先,經過以下代碼建立代理類,表示建立SqlSessionFactory的代理類的實例,該代理類實現SqlSession接口,定義了方法攔截器,若是調用代理類實例中實現SqlSession接口定義的方法,該調用則被導向SqlSessionInterceptor的invoke方法緩存
1 public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType, 2 PersistenceExceptionTranslator exceptionTranslator) { 3 4 notNull(sqlSessionFactory, "Property 'sqlSessionFactory' is required"); 5 notNull(executorType, "Property 'executorType' is required"); 6 7 this.sqlSessionFactory = sqlSessionFactory; 8 this.executorType = executorType; 9 this.exceptionTranslator = exceptionTranslator; 10 this.sqlSessionProxy = (SqlSession) newProxyInstance( 11 SqlSessionFactory.class.getClassLoader(), 12 new Class[] { SqlSession.class }, 13 new SqlSessionInterceptor()); 14 }
核心代碼就在 SqlSessionInterceptor的invoke方法當中。安全
1 private class SqlSessionInterceptor implements InvocationHandler { 2 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { 3 //獲取SqlSession(這個SqlSession纔是真正使用的,它不是線程安全的) 4 //這個方法能夠根據Spring的事物上下文來獲取事物範圍內的sqlSession 5 //一會咱們在分析這個方法 6 final SqlSession sqlSession = getSqlSession( 7 SqlSessionTemplate.this.sqlSessionFactory, 8 SqlSessionTemplate.this.executorType, 9 SqlSessionTemplate.this.exceptionTranslator); 10 try { 11 //調用真實SqlSession的方法 12 Object result = method.invoke(sqlSession, args); 13 //而後判斷一下當前的sqlSession是否被Spring託管 若是未被Spring託管則自動commit 14 if (!isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) { 15 // force commit even on non-dirty sessions because some databases require 16 // a commit/rollback before calling close() 17 sqlSession.commit(true); 18 } 19 //返回執行結果 20 return result; 21 } catch (Throwable t) { 22 //若是出現異常則根據狀況轉換後拋出 23 Throwable unwrapped = unwrapThrowable(t); 24 if (SqlSessionTemplate.this.exceptionTranslator != null && unwrapped instanceof PersistenceException) { 25 Throwable translated = SqlSessionTemplate.this.exceptionTranslator.translateExceptionIfPossible((PersistenceException) unwrapped); 26 if (translated != null) { 27 unwrapped = translated; 28 } 29 } 30 throw unwrapped; 31 } finally { 32 //關閉sqlSession 33 //它會根據當前的sqlSession是否在Spring的事物上下文當中來執行具體的關閉動做 34 //若是sqlSession被Spring管理 則調用holder.released(); 使計數器-1 35 //不然才真正的關閉sqlSession 36 closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory); 37 } 38 } 39 }
在上面的invoke方法當中使用了倆個工具方法 分別是session
SqlSessionUtils.getSqlSession(SqlSessionFactory sessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator)mybatis
SqlSessionUtils.closeSqlSession(SqlSession session, SqlSessionFactory sessionFactory)app
那麼這個倆個方法又是如何與Spring的事物進行關聯的呢?工具
1 public static SqlSession getSqlSession(SqlSessionFactory sessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator) { 2 //根據sqlSessionFactory從當前線程對應的資源map中獲取SqlSessionHolder,當sqlSessionFactory建立了sqlSession,就會在事務管理器中添加一對映射:key爲sqlSessionFactory,value爲SqlSessionHolder,該類保存sqlSession及執行方式 3 SqlSessionHolder holder = (SqlSessionHolder) getResource(sessionFactory); 4 //若是holder不爲空,且和當前事務同步 5 if (holder != null && holder.isSynchronizedWithTransaction()) { 6 //hodler保存的執行類型和獲取SqlSession的執行類型不一致,就會拋出異常,也就是說在同一個事務中,執行類型不能變化,緣由就是同一個事務中同一個sqlSessionFactory建立的sqlSession會被重用 7 if (holder.getExecutorType() != executorType) { 8 throw new TransientDataAccessResourceException("Cannot change the ExecutorType when there is an existing transaction"); 9 } 10 //增長該holder,也就是同一事務中同一個sqlSessionFactory建立的惟一sqlSession,其引用數增長,被使用的次數增長 11 holder.requested(); 12 //返回sqlSession 13 return holder.getSqlSession(); 14 } 15 //若是找不到,則根據執行類型構造一個新的sqlSession 16 SqlSession session = sessionFactory.openSession(executorType); 17 //判斷同步是否激活,只要SpringTX被激活,就是true 18 if (isSynchronizationActive()) { 19 //加載環境變量,判斷註冊的事務管理器是不是SpringManagedTransaction,也就是Spring管理事務 20 Environment environment = sessionFactory.getConfiguration().getEnvironment(); 21 if (environment.getTransactionFactory() instanceof SpringManagedTransactionFactory) { 22 //若是是,則將sqlSession加載進事務管理的本地線程緩存中 23 holder = new SqlSessionHolder(session, executorType, exceptionTranslator); 24 //以sessionFactory爲key,hodler爲value,加入到TransactionSynchronizationManager管理的本地緩存ThreadLocal<Map<Object, Object>> resources中 25 bindResource(sessionFactory, holder); 26 //將holder, sessionFactory的同步加入本地線程緩存中ThreadLocal<Set<TransactionSynchronization>> synchronizations 27 registerSynchronization(new SqlSessionSynchronization(holder, sessionFactory)); 28 //設置當前holder和當前事務同步 29 holder.setSynchronizedWithTransaction(true); 30 //增長引用數 31 holder.requested(); 32 } else { 33 if (getResource(environment.getDataSource()) == null) { 34 } else { 35 throw new TransientDataAccessResourceException( 36 "SqlSessionFactory must be using a SpringManagedTransactionFactory in order to use Spring transaction synchronization"); 37 } 38 } 39 } else { 40 } 41 return session; 42 }
1 public static void closeSqlSession(SqlSession session, SqlSessionFactory sessionFactory) { 2 //其實下面就是判斷session是否被Spring事務管理,若是管理就會獲得holder 3 SqlSessionHolder holder = (SqlSessionHolder) getResource(sessionFactory); 4 if ((holder != null) && (holder.getSqlSession() == session)) { 5 //這裏釋放的做用,不是關閉,只是減小一下引用數,由於後面可能會被複用 6 holder.released(); 7 } else { 8 //若是不是被spring管理,那麼就不會被Spring去關閉回收,就須要本身close 9 session.close(); 10 } 11 }
其實經過上面的代碼咱們能夠看出 Mybatis在不少地方都用到了代理模式,這個模式能夠說是一種經典模式,其實不牢牢在Mybatis當中使用普遍,Spring的事物,AOP ,鏈接池技術 等技術都使用了代理技術。在後面的文章中咱們來分析Spring的抽象事物管理機制。