Mybatis SqlSessionTemplate 源碼解析 原理理解
Mybatis SqlSessionTemplate 源碼解析
在使用Mybatis與Spring集成的時候我們用到了SqlSessionTemplate 這個類。
<bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate"> <constructor-arg index="0" ref="sqlSessionFactory" /> </bean>
通過源碼我們何以看到 SqlSessionTemplate 實現了SqlSession接口,也就是說我們可以使用SqlSessionTemplate 來代理以往的DefailtSqlSession完成對數據庫的操作,但是DefailtSqlSession這個類不是線程安全的,所以這個類不可以被設置成單例模式的。
如果是常規開發模式 我們每次在使用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方法當中使用了倆個工具方法 分別是
SqlSessionUtils.getSqlSession(SqlSessionFactory sessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator)
SqlSessionUtils.closeSqlSession(SqlSession session, SqlSessionFactory sessionFactory)
那麽這個倆個方法又是如何與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 ,連接池技術 等技術都使用了代理技術。
Mybatis SqlSessionTemplate 源碼解析 原理理解