(4)一起來看下mybatis框架的緩存原理吧

  • 2019 年 10 月 14 日
  • 筆記

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

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

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這個對象有什麼屬性吧

 

 我們可以看到,有這條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專題見啦