SqlSessionTemplate探究

 問題就是:不管是多個dao使用一個SqlSessionTemplate,仍是一個dao使用一個SqlSessionTemplate,SqlSessionTemplate都是對應一個sqlSession,當多個web線程調用同一個dao時,它們使用的是同一個SqlSessionTemplate,也就是同一個SqlSession,如何保證線程安全,關鍵就在於代理:web

(1)首先,經過以下代碼建立代理類,表示建立SqlSessionFactory的代理類的實例,該代理類實現SqlSession接口,定義了方法攔截器,若是調用代理類實例中實現SqlSession接口定義的方法,該調用則被導向SqlSessionInterceptor的invoke方法spring

 
  1. this.sqlSessionProxy = (SqlSession) newProxyInstance( 
  2.         SqlSessionFactory.class.getClassLoader(), 
  3.         new Class[] { SqlSession.class }, 
  4.         new SqlSessionInterceptor()); 

(2)因此關鍵之處轉移到invoke方法中,代碼以下,該類的註釋是代理將Mybatis的方法調用導向從Spring的事務管理器獲取的合適的SqlSession,說明雖然都是調用一樣一個SqlSession接口,可是實際執行sql的sqlSession會有所不一樣。sql

 
  1. public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { 
  2.       //調用SqlSessionUtils的getSqlSession方法從Spring的事務管理器獲取合適的SqlSession 
  3.    final SqlSession sqlSession = getSqlSession( 
  4.           SqlSessionTemplate.this.sqlSessionFactory, 
  5.           SqlSessionTemplate.this.executorType, 
  6.           SqlSessionTemplate.this.exceptionTranslator); 
  7.       try { 
  8.   //經過sqlSession對象調用該方法 
  9.         Object result = method.invoke(sqlSession, args); 
  10.         //判斷sqlSession是否被Spring事務管理,也就是sqlSession被放在Spring事務管理的本地線程緩存中。若是不是,則須要本身提交。若是是,則Spring經過代理機制,進行提交和回滾 
  11.         if (!isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) { 
  12.           // force commit even on non-dirty sessions because some databases require 
  13.           // a commit/rollback before calling close() 
  14.           sqlSession.commit(true); 
  15.         } 
  16.   //返回方法的調用結果 
  17.         return result; 
  18.       } catch (Throwable t) { 
  19.   //若是出現異常,則利用異常轉換器將Mybatis的異常轉爲Spring的DataAccessException 
  20.         Throwable unwrapped = unwrapThrowable(t); 
  21.         if (SqlSessionTemplate.this.exceptionTranslator != null && unwrapped instanceof PersistenceException)     { 
  22.           Throwable translated = SqlSessionTemplate.this.exceptionTranslator.translateExceptionIfPossible((PersistenceException) unwrapped); 
  23.           if (translated != null) { 
  24.             unwrapped = translated; 
  25.           } 
  26.         } 
  27.         throw unwrapped; 
  28.       } finally { 
  29.   //方法調用完畢後,關閉sqlSession鏈接 
  30.         closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory); 
  31.       } 
  32.     } 

其中,核心有兩部分:數據庫

(3)如何從Spring的事務管理器中得到合適的sqlSession,從而保證線程安全,很明顯全部dao的多個線程不是使用同一個sqlSession,否則其中一個closeSqlSession,其餘怎麼用。緩存

該方法的註釋:從Spring事務管理器中獲得一個SqlSession,若是須要建立一個新的。首先努力從當前事務以外獲得一個SqlSession,若是沒有就創造一個新的。而後,若是Spring TX被激活,也就是事務被打開,且事務管理器是SpringManagedTransactionFactory時,將獲得的SqlSession同當前事務同步,下面是該函數的核心代碼安全

 
  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.   } 

上述代碼中能夠看出,只有在一個線程的一個事務中,由同一個sqlSessionFactory建立的執行類型相同的sqlSession纔會被複用,其餘狀況下都是建立新的sqlSession。試想一下,即便只有一個SqlSessionTemplate供全部dao使用,全部地方使用的都是同以個sqlSessionFactory,可是因爲是不一樣線程,因此獲得的不是同一個sqlSession,所以不會出現線程安全問題。好處是同一個線程同一個事務中sqlSession會被複用,不會每執行一個sql請求,都建立一個SqlSession,這樣很浪費資源,由於SqlSession至關於一次數據庫鏈接。session

(4)如何關閉sqlSession鏈接,主要代碼以下app

 
  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.   } 
相關文章
相關標籤/搜索