Mybatis sqlsession解析

1、sqlsession獲取過程spring

一、基礎配置sql

  在mybatis框架下進行的數據庫操做都須要首先獲取sqlsession,在mybatis與spring集成後獲取sqlsession須要用到sqlsessionTemplate這個類。數據庫

首先在spring對sqlsessionTemplate進行配置,使用到的是 org.mybatis.spring.SqlSessionTemplate 這個類。緩存

<!-- SqlSession實例 -->
<bean id="sessionTemplate" class="org.mybatis.spring.SqlSessionTemplate"
        destroy-method="close">
<!--當構造函數有多個參數時,能夠使用constructor-arg標籤的index屬性,index屬性的值從0開始,這裏將sqlsessionFactory做爲第一個參數傳入--> 
<constructor-arg index="0" ref="sqlSessionFactory" /> 
</bean>
<!-- 將數據源映射到sqlSessionFactory中 -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
    <property name="configLocation" value="classpath:mybatis/mybatis-config.xml" />
    <property name="dataSource" ref="dataSource" />
</bean>

因此在sqlsessionTemplate的初始化過程當中,首先會將sqlsessionFactory做爲參數傳入,sqlsessionFactory中映射了數據源信息。安全

配置事務,在後來的sqlsession獲取過程當中會對事務進行判斷session

<!--======= 事務配置 Begin ================= -->
<!-- 事務管理器(由Spring管理MyBatis的事務) -->
<bean id="transactionManager"
        class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <!-- 關聯數據源 -->
    <property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 註解事務 -->
<tx:annotation-driven transaction-manager="transactionManager" />
<!--======= 事務配置 End =================== -->

二、sqlsessionTemplate的初始化mybatis

public class SqlSessionTemplate implements SqlSession {
    private final SqlSessionFactory sqlSessionFactory;
    private final ExecutorType executorType;
    private final SqlSession sqlSessionProxy;
    private final PersistenceExceptionTranslator exceptionTranslator;

    public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
        this(sqlSessionFactory, sqlSessionFactory.getConfiguration().getDefaultExecutorType());
    }

    public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType) {
        this(sqlSessionFactory, executorType, new MyBatisExceptionTranslator(sqlSessionFactory.getConfiguration().getEnvironment().getDataSource(), true));
    }

    public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator) {
        Assert.notNull(sqlSessionFactory, "Property 'sqlSessionFactory' is required");
        Assert.notNull(executorType, "Property 'executorType' is required");
        this.sqlSessionFactory = sqlSessionFactory;
        this.executorType = executorType;
        this.exceptionTranslator = exceptionTranslator;
        this.sqlSessionProxy = (SqlSession)Proxy.newProxyInstance(SqlSessionFactory.class.getClassLoader(), new Class[]{SqlSession.class}, new SqlSessionTemplate.SqlSessionInterceptor());
    }

  SqlsessionTemplate類的最開始初始化過程當中,首先會經過sqlsessionFactory參數進行構造,經過Proxy.newProxyInstance()方法來建立代理類,表示建立SqlSessionFactory的代理類的實例,該代理類實現SqlSession接口,定義了方法攔截器,若是調用代理類實例中實現SqlSession接口定義的方法,該調用則被導向SqlsessionTemplate的一個內部類SqlSessionInterceptor的invoke方法,最終初始化sqlsessionProxy。app

三、sqlsession的調用過程框架

因爲上面兩個過程當中已經將sqlsessionTemplate中的sqlsessionProxy已經初始化完畢,因此在代碼中能夠進行調用。調用最終都會進入SqlSessionInterceptor的invoke方法。分佈式

private class SqlSessionInterceptor implements InvocationHandler {
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
      //獲取SqlSession(這個SqlSession纔是真正使用的,它不是線程安全的)
      //這個方法能夠根據Spring的事物上下文來獲取事物範圍內的sqlSession
      final SqlSession sqlSession = SqlSessionUtils.getSqlSession(
          SqlSessionTemplate.this.sqlSessionFactory,
          SqlSessionTemplate.this.executorType,
          SqlSessionTemplate.this.exceptionTranslator);
      try {
        //調用真實SqlSession的方法
        Object result = method.invoke(sqlSession, args);
        //而後判斷一下當前的sqlSession是否有配置Spring事務 若是沒有自動commit
        if (!SqlSessionUtils.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) {
          Throwable translated = SqlSessionTemplate.this.exceptionTranslator.translateExceptionIfPossible((PersistenceException) unwrapped);
          if (translated != null) {
            unwrapped = translated;
          }
        }
        throw unwrapped;
      } finally {
        //關閉sqlSession
        //它會根據當前的sqlSession是否在Spring的事務上下文當中來執行具體的關閉動做
        //若是sqlSession被Spring事務管理 則調用holder.released(); 使計數器-1
        //不然才真正的關閉sqlSession
        SqlSessionUtils.closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
      }
    }
  }

在上面的代碼中用到兩個很關鍵的方法:

  獲取sqlsession方法:SqlSessionUtils.getSqlSession(SqlSessionTemplate.this.sqlSessionFactory, SqlSessionTemplate.this.executorType, SqlSessionTemplate.this.exceptionTranslator);

  關閉sqlsession方法:SqlSessionUtils.closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);

public static SqlSession getSqlSession(SqlSessionFactory sessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator) {     
    //根據sqlSessionFactory從當前線程對應的資源map中獲取SqlSessionHolder,當sqlSessionFactory建立了sqlSession,就會在事務管理器中添加一對映射:key爲sqlSessionFactory,value爲SqlSessionHolder,該類保存sqlSession及執行方式 
    SqlSessionHolder holder = (SqlSessionHolder) getResource(sessionFactory); 
 //若是holder不爲空,且和當前事務同步 
    if (holder != null && holder.isSynchronizedWithTransaction()) { 
      //hodler保存的執行類型和獲取SqlSession的執行類型不一致,就會拋出異常,也就是說在同一個事務中,執行類型不能變化,緣由就是同一個事務中同一個sqlSessionFactory建立的sqlSession會被重用 
      if (holder.getExecutorType() != executorType) { 
        throw new TransientDataAccessResourceException("Cannot change the ExecutorType when there is an existing transaction"); 
      } 
      //增長該holder,也就是同一事務中同一個sqlSessionFactory建立的惟一sqlSession,其引用數增長,被使用的次數增長 
      holder.requested(); 
   //返回sqlSession 
      return holder.getSqlSession(); 
    } 
 //若是找不到,則根據執行類型構造一個新的sqlSession 
    SqlSession session = sessionFactory.openSession(executorType); 
 //判斷同步是否激活,只要SpringTX被激活,就是true 
    if (isSynchronizationActive()) { 
   //加載環境變量,判斷註冊的事務管理器是不是SpringManagedTransaction,也就是Spring管理事務 
      Environment environment = sessionFactory.getConfiguration().getEnvironment(); 
      if (environment.getTransactionFactory() instanceof SpringManagedTransactionFactory) { 
  //若是是,則將sqlSession加載進事務管理的本地線程緩存中 
        holder = new SqlSessionHolder(session, executorType, exceptionTranslator); 
  //以sessionFactory爲key,hodler爲value,加入到TransactionSynchronizationManager管理的本地緩存ThreadLocal<Map<Object, Object>> resources中 
        bindResource(sessionFactory, holder); 
  //將holder, sessionFactory的同步加入本地線程緩存中ThreadLocal<Set<TransactionSynchronization>> synchronizations 
        registerSynchronization(new SqlSessionSynchronization(holder, sessionFactory)); 
        //設置當前holder和當前事務同步 
  holder.setSynchronizedWithTransaction(true); 
  //增長引用數 
        holder.requested(); 
      } else { 
        if (getResource(environment.getDataSource()) == null) { 
        } else { 
          throw new TransientDataAccessResourceException( 
              "SqlSessionFactory must be using a SpringManagedTransactionFactory in order to use Spring transaction synchronization"); 
        } 
      } 
    } else { 
    } 
    return session; 
  }
public static void closeSqlSession(SqlSession session, SqlSessionFactory sessionFactory) { 
 //其實下面就是判斷session是否被Spring事務管理,若是管理就會獲得holder  
    SqlSessionHolder holder = (SqlSessionHolder) getResource(sessionFactory); 
    if ((holder != null) && (holder.getSqlSession() == session)) { 
   //這裏釋放的做用,不是關閉,只是減小一下引用數,由於後面可能會被複用 
      holder.released(); 
    } else { 
   //若是不是被spring管理,那麼就不會被Spring去關閉回收,就須要本身close 
      session.close(); 
    } 
  }

2、mybatis的緩存

一、一級緩存

  Mybatis的一級緩存是默認開啓的,主要是經過sqlsession來實現的,每一個sqlsession對象會在本地建立一個緩存(local cache),對於每次查詢都會在本地緩存中進行查詢,若是命中則直接返回,若是沒查到則進入到數據庫中進行查找。一級緩存是sqlsession級別的。

  從上面sqlsession的獲取源碼中能夠看到,每次獲取一個全新的sqlsession最終都是會保存在ThreadLocal中跟線程綁定,若是在spring中配置了事務則整個事務週期裏面都共享一個sqlsession,若是沒有配置事務則每次請求都是一個獨立的sqlsession。每次執行完後數據庫操做後,若是還在事務週期中只對sqlsession的引用次數減一,不然直接關閉sqlsession。

一級緩存執行的時序圖:

 

小結:

MyBatis一級緩存的生命週期和SqlSession一致。
MyBatis一級緩存內部設計簡單,只是一個沒有容量限定的HashMap,在緩存的功能性上有所欠缺。
MyBatis的一級緩存最大範圍是SqlSession內部,有多個SqlSession或者分佈式的環境下,數據庫寫操做會引發髒數據,建議設定緩存級別爲Statement。

二、二級緩存

在系統中若是須要使用二級緩存則直接在spring中進行配置聲明便可。

<!-- 這個配置使全局的映射器啓用或禁用 緩存 -->
<setting name="cacheEnabled" value="true" />
<!-- 開啓二級緩存開關 -->
<cache/>

  MyBatis的二級緩存相對於一級緩存來講,實現了SqlSession之間緩存數據的共享,同時粒度更加的細,可以到namespace級別,經過Cache接口實現類不一樣的組合,對Cache的可控性也更強。  MyBatis在多表查詢時,極大可能會出現髒數據,有設計上的缺陷,安全使用二級緩存的條件比較苛刻。  在分佈式環境下,因爲默認的MyBatis Cache實現都是基於本地的,分佈式環境下必然會出現讀取到髒數據,須要使用集中式緩存將MyBatis的Cache接口實現,有必定的開發成本,直接使用Redis,Memcached等分佈式緩存可能成本更低,安全性也更高。

相關文章
相關標籤/搜索