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 ,連接配接池技術 等技術都使用了代理技術。在後面的文章中我們來分析Spring的抽象事物管理機制。