Mybatis SqlSessionTemplate 源碼解析

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的抽象事物管理機制。

相關文章
相關標籤/搜索