天天看點

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