1. 程式人生 > >(4)一起來看下mybatis框架的快取原理吧

(4)一起來看下mybatis框架的快取原理吧

本文是作者原創,版權歸作者所有.若要轉載,請註明出處.本文只貼我覺得比較重要的原始碼,其他不重要非關鍵的就不貼了

我們知道.使用快取可以更快的獲取資料,避免頻繁直接查詢資料庫,節省資源.

MyBatis快取有一級快取和二級快取.

1.一級快取也叫本地快取,預設開啟,在一個sqlsession內有效.當在同一個sqlSession裡面發出同樣的sql查詢請求,Mybatis會直接從快取中查詢。如果沒有則從資料庫查詢

 

下面我們貼一下一級快取的測試結果

String resource = "mybatis.xml";
    //讀取配置檔案,生成讀取流
    InputStream inputStream = Resources.getResourceAsStream(resource);
    //返回的DefaultSqlSessionFactory是SqlSessionFactory介面的實現類,
    //這個類只有一個屬性,就是Configuration物件,Configuration物件用來存放讀取xml配置的資訊
    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
    //SqlSession是與資料庫打交道的物件 SqlSession物件中有上面傳來的Configuration物件,
    //SqlSession物件還有executor處理器物件,executor處理器有一個dirty屬性,預設為false
    //返回的DefaultSqlSession是SqlSession介面的實現類
    SqlSession sqlSession = sqlSessionFactory.openSession();
    //SqlSession sqlSession2 = sqlSessionFactory.openSession();
    //通過動態代理實現介面 ,用動態代理物件去幫我們執行SQL
    //這裡生成mapper實際型別是org.apache.ibatis.binding.MapperProxy
    DemoMapper mapper = sqlSession.getMapper(DemoMapper.class);
    DemoMapper mapper2 = sqlSession.getMapper(DemoMapper.class);
    //這裡用生成的動態代理物件執行
    String projId="0124569b738e405fb20b68bfef37f487";
    String sectionName="標段";
    List<ProjInfo> projInfos = mapper.selectAll(projId, sectionName);
    List<ProjInfo> projInfos2 = mapper2.selectAll(projId, sectionName);
    System.out.println(projInfos.hashCode());//這裡和下面那條的查詢結果的hashcode是一樣的
    System.out.println(projInfos2.hashCode());//
    sqlSession.close();

    sqlSession = sqlSessionFactory.openSession();
    DemoMapper mapper5 = sqlSession.getMapper(DemoMapper.class);
    List<ProjInfo> projInfos5 = mapper5.selectAll(projId, sectionName);
    System.out.println(projInfos5.hashCode());//這裡和上面兩條的查詢結果的hashcode是不一樣的

 好,我們可以看到,上面兩條的查詢結果的hashcode一樣,第三條不一樣,一級快取生效了

 

2.二級快取是mapper級別的,就是說二級快取是以Mapper配置檔案的namespace為單位建立的。

  二級快取預設是不開啟的,需要手動開啟二級快取,如下需要在mybatis配置檔案中的settting標籤裡面加入開啟

<settings>
        <!-- 開啟二級快取。value值填true -->
        <setting name="cacheEnabled" value="true"/>
        <!--  配置預設的執行器。SIMPLE 就是普通的執行器;
        REUSE 執行器會重用預處理語句(prepared statements); BATCH 執行器將重用語句並執行批量更新。預設SIMPLE  -->
        <setting name="defaultExecutorType" value="SIMPLE"/>
    </settings>

實現二級快取的時候,MyBatis要求返回的物件必須是可序列化的,如圖

 

 還要在mapper,xml檔案中新增cache標籤,標籤裡的readOnly屬性需填true   如下:

<cache readOnly="true" ></cache>

    <select id="selectAll" resultType="com.lusaisai.po.ProjInfo">
        SELECT id AS sectionId,section_name AS sectionName,proj_id AS projId
        FROM b_proj_section_info
        WHERE proj_id=#{projId} AND section_name LIKE CONCAT('%',#{sectionName},'%')
    </select>

 我們再來看下二級快取的執行結果

  我們可以看到二級快取也生效了.

 

下面來看看快取的原始碼,大家猜底層是使用什麼實現的,前面的流程就不一一看了,主要貼一下快取的程式碼,

public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
    //
    BoundSql boundSql = ms.getBoundSql(parameterObject);
    CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
    //這個跟進去看戲,應該是真正的jdbc操作了
    return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
  }

我們可以看到,上面的生成了一個快取的key,具體怎麼實現的就先不看了,看下key這個物件有什麼屬性吧

 

 我們可以看到,key物件有這條sql語句除了結果之外的所有資訊,還有hashcode等等,我們繼續跟進去

public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
      throws SQLException {
    //MappedStatement的作用域是全域性共享的,這裡的cache是介面,有多個實現類
    Cache cache = ms.getCache();
    if (cache != null) {
      flushCacheIfRequired(ms);
      if (ms.isUseCache() && resultHandler == null) {
        ensureNoOutParams(ms, boundSql);
        @SuppressWarnings("unchecked")
        List<E> list = (List<E>) tcm.getObject(cache, key);
        if (list == null) {
          list = delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
          tcm.putObject(cache, key, list); // issue #578 and #116
        }
        return list;
      }
    }
    //前面是快取處理跟進去
    return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
  }

我們看下Cache 的實現類

 

有很多,這裡就不深究了,繼續往下看,我們看下list = delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql)這行程式碼,這是處理一級快取的方法,點進去看下

這裡用到我們前面生成的key了

public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
    ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());
    if (closed) {
      throw new ExecutorException("Executor was closed.");
    }
    if (queryStack == 0 && ms.isFlushCacheRequired()) {
      clearLocalCache();
    }
    List<E> list;
    try {
      queryStack++;
      list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
      if (list != null) {
        handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
      } else {
        //進這裡,繼續跟
        list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
      }
    } finally {
      queryStack--;
    }
    if (queryStack == 0) {
      for (DeferredLoad deferredLoad : deferredLoads) {
        deferredLoad.load();
      }
      // issue #601
      deferredLoads.clear();
      if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
        // issue #482
        clearLocalCache();
      }
    }
    return list;
  }

我們關注下list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;這行程式碼,點進去看下

@Override
  public Object getObject(Object key) {
    return cache.get(key);
  }

我截個圖,看下上圖的PerpetualCache物件底層是怎麼實現的

 

 好,很明顯了,這裡一級快取是用的hashmap實現的.

 

下面我們看下二級快取的程式碼,上面的List<E> list = (List<E>) tcm.getObject(cache, key);這行程式碼跟進去

public Object getObject(Cache cache, CacheKey key) {
    return getTransactionalCache(cache).getObject(key);
  }

 繼續跟

@Override
  public Object getObject(Object key) {
    // issue #116
    Object object = delegate.getObject(key);
    if (object == null) {
      entriesMissedInCache.add(key);
    }
    // issue #146
    if (clearOnCommit) {
      return null;
    } else {
      return object;
    }
  }

截個圖看下delegate物件的屬性,很多層

 

 我們可以看到所有資料都在這裡了.網上找的圖

 再來一段快取的關鍵程式碼,這裡就是包裝了一層層物件的程式碼

private Cache setStandardDecorators(Cache cache) {
    try {
      MetaObject metaCache = SystemMetaObject.forObject(cache);
      if (size != null && metaCache.hasSetter("size")) {
        metaCache.setValue("size", size);
      }
      if (clearInterval != null) {
        cache = new ScheduledCache(cache);
        ((ScheduledCache) cache).setClearInterval(clearInterval);
      }
      if (readWrite) {
        cache = new SerializedCache(cache);
      }
      cache = new LoggingCache(cache);
      cache = new SynchronizedCache(cache);
      if (blocking) {
        cache = new BlockingCache(cache);
      }
      return cache;
    } catch (Exception e) {
      throw new CacheException("Error building standard cache decorators.  Cause: " + e, e);
    }
  }

好,再來張圖總結一下

 

 最後,如果我們和spring整合,那麼此時mybatis一級快取就會失效,因為sqlsession交給spring管理,會自動關閉session.

關於如何和spring整合就後面再來講吧,下面一段時間,我會先研究一下spring原始碼,spring專題見啦

&n