在MyBatis架構中SqlSession是提供給外層調用的頂層接口,實現類有:DefaultSqlSession、SqlSessionManager以及mybatis-spring提供的實現SqlSessionTemplate。默認的實現類爲DefaultSqlSession如。類圖結構以下所示:
對於MyBatis提供的原生實現類來講,用的最多的就是DefaultSqlSession,但咱們知道DefaultSqlSession這個類不是線程安全的!以下:
web
而在咱們開發的時候確定會用到Spring,也會用到mybatis-spring框架,在使用MyBatis與Spring集成的時候咱們會用到了SqlSessionTemplate這個類,例以下邊的配置,注入一個單例的SqlSessionTemplate對象:面試
<bean id="sqlSessionTemplate" class="org.mybatis.spring.SqlSessionTemplate"> <constructor-arg ref="sqlSessionFactory" /> </bean>
SqlSessionTemplate的源代碼註釋以下:
經過源碼咱們何以看到 SqlSessionTemplate實現了SqlSession接口,也就是說咱們可使用SqlSessionTemplate來代理以往的DefaultSqlSession完成對數據庫的操做,可是DefaultSqlSession這個類不是線程安全的,因此DefaultSqlSession這個類不能夠被設置成單例模式的。spring
若是是常規開發模式的話,咱們每次在使用DefaultSqlSession的時候都從SqlSessionFactory當中獲取一個就能夠了。可是與Spring集成之後,Spring提供了一個全局惟一的SqlSessionTemplate對象來完成DefaultSqlSession的功能,問題就是:不管是多個Dao使用一個SqlSessionTemplate,仍是一個Dao使用一個SqlSessionTemplate,SqlSessionTemplate都是對應一個sqlSession對象,當多個web線程調用同一個Dao時,它們使用的是同一個SqlSessionTemplate,也就是同一個SqlSession,那麼它是如何確保線程安全的呢?讓咱們一塊兒來分析一下:sql
(1)首先,經過以下代碼建立代理類,表示建立SqlSessionFactory的代理類的實例,該代理類實現SqlSession接口,定義了方法攔截器,若是調用代理類實例中實現SqlSession接口定義的方法,該調用則被導向SqlSessionInterceptor的invoke方法(代理對象的InvocationHandler就是SqlSessionInterceptor,若是把它命名爲SqlSessionInvocationHandler則更好理解!)
核心代碼就在 SqlSessionInterceptor的invoke方法當中。數據庫
private class SqlSessionInterceptor implements InvocationHandler { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { //獲取SqlSession(這個SqlSession纔是真正使用的,它不是線程安全的) //這個方法能夠根據Spring的事務上下文來獲取事物範圍內的sqlSession SqlSession sqlSession = getSqlSession( SqlSessionTemplate.this.sqlSessionFactory, SqlSessionTemplate.this.executorType, SqlSessionTemplate.this.exceptionTranslator); try { //調用從Spring的事物上下文獲取事物範圍內的sqlSession對象 Object result = method.invoke(sqlSession, args); //而後判斷一下當前的sqlSession是否被Spring託管 若是未被Spring託管則自動commit if (!isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) { // force commit even on non-dirty sessions because some databases require // a commit/rollback before calling close() sqlSession.commit(true); } return result; } catch (Throwable t) { //若是出現異常則根據狀況轉換後拋出 Throwable unwrapped = unwrapThrowable(t); if (SqlSessionTemplate.this.exceptionTranslator != null && unwrapped instanceof PersistenceException) { // release the connection to avoid a deadlock if the // translator is no loaded. See issue #22 closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory); sqlSession = null; Throwable translated = SqlSessionTemplate.this.exceptionTranslator. translateExceptionIfPossible((PersistenceException) unwrapped); if (translated != null) { unwrapped = translated; } } throw unwrapped; } finally { if (sqlSession != null) { //關閉sqlSession,它會根據當前的sqlSession是否在Spring的事物上下文當中來執行具體的關閉動做 //若是sqlSession被Spring管理 則調用holder.released(); //使計數器-1,不然才真正的關閉sqlSession closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory); } } } }
在上面的invoke方法當中使用了兩個工具方法分別是:安全
(1)SqlSessionUtils.getSqlSession(SqlSessionFactory sessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator) (2)SqlSessionUtils.closeSqlSession(SqlSession session, SqlSessionFactory sessionFactory)
那麼這兩個方法又是如何與Spring的事物進行關聯的呢?session
一、getSqlSession方法以下:mybatis
public static SqlSession getSqlSession(SqlSessionFactory sessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator) { notNull(sessionFactory, NO_SQL_SESSION_FACTORY_SPECIFIED); notNull(executorType, NO_EXECUTOR_TYPE_SPECIFIED); //根據sqlSessionFactory從當前線程對應的資源map中獲取SqlSessionHolder, // 當sqlSessionFactory建立了sqlSession, //就會在事務管理器中添加一對映射:key爲sqlSessionFactory,value爲SqlSessionHolder, // 該類保存sqlSession及執行方式 SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager. getResource(sessionFactory); //從SqlSessionHolder中提取SqlSession對象 SqlSession session = sessionHolder(executorType, holder); if (session != null) { return session; } if (LOGGER.isDebugEnabled()) { LOGGER.debug("Creating a new SqlSession"); } //若是當前事物管理器中獲取不到SqlSessionHolder對象就從新建立一個 session = sessionFactory.openSession(executorType); //將新建立的SqlSessionHolder對象註冊到TransactionSynchronizationManager中 registerSessionHolder(sessionFactory, executorType, exceptionTranslator, session); return session; }
二、closeSqlSession方法以下:架構
//刪除了部分日誌代碼 public static void closeSqlSession(SqlSession session, SqlSessionFactory sessionFactory) { //其實下面就是判斷session是否被Spring事務管理,若是管理就會獲得holder SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory); if ((holder != null) && (holder.getSqlSession() == session)) { //這裏釋放的做用,不是關閉,只是減小一下引用數,由於後面可能會被複用 holder.released(); } else { //若是不是被spring管理,那麼就不會被Spring去關閉回收,就須要本身close session.close(); } }
大體的分析到此爲止,可能有些許不夠順暢,不過:紙上得來終覺淺,絕知此事要躬行!還但願小夥伴打開本身的編譯器,找到此處的代碼,認真走一遍流程!app
其實經過上面的代碼咱們能夠看出Mybatis在不少地方都用到了代理模式,代理模式能夠說是一種經典模式,其實不牢牢在這個地方用到了代理模式,Spring的事物、AOP、Mybatis數據庫鏈接池技術、MyBatis的核心原理(如何在只有接口沒有實現類的狀況下完成數據庫的操做!)等技術都使用了代理技術。
上述說了SqlSession的實現還有一個SqlSessionManager,那麼SqlSessionManager究竟是什麼個東西哪?且看定義以下:
你可能會發現SqlSessionManager的構造方法居然是private的,那咱們怎麼建立這個對象哪?其實SqlSessionManager建立對象是經過newInstance的方法建立對象的,但須要注意的是他雖然有私有的構造方法,而且提供給咱們了一個公有的newInstance方法,但它並非一個單例模式!
newInstance有不少重載的方法,以下所示:
SqlSessionManager的openSession方法及其重載的方法是直接經過調用其中底層封裝的SqlSessionFactory對象的openSession方法來建立SqlSession對象的,重載方法以下:
SqlSessionManager中實現了SqlSession接口中的方法,例如:select、update等,都是直接調用sqlSessionProxy代理對象中相應的方法。在建立該代理對像的時候使用的InvocationHandler對象是SqlSessionInterceptor,他是定義在SqlSessionManager的一個內部類,其定義以下:
綜上所述,咱們應該大體瞭解了DefaultSqlSession和SqlSessionManager之間的區別:
一、DefaultSqlSession的內部沒有提供像SqlSessionManager同樣經過ThreadLocal的方式來保證線程的安全性;
二、SqlSessionManager是經過localSqlSession這個ThreadLocal變量,記錄與當前線程綁定的SqlSession對象,供當前線程循環使用,從而避免在同一個線程屢次建立SqlSession對象形成的性能損耗;
三、DefaultSqlSession不是線程安全的,咱們在進行原生開發的時候,須要每次爲一個操做都建立一個SqlSession對象,其性能可想而知;
那麼問題來了:
一、爲何mybatis-spring框架中不直接使用線程安全的SqlSessionManager(SqlSessionFactory它是線程安全的)而是使用DefaultSqlSession這個線程不安全的類,並經過動態代理的方式來保證DefaultSqlSession操做的線程安全性哪?
二、DefaultSqlSession中是如何經過Executor來表現策略模式的或者DefaultSqlSession如何使用策略模式模式的?