淺談mybatis-Cache


每次在編寫文章之前自己都喜歡先來個感想,今天的話題是多讀英文文檔,好處多多!什麼情況下要考慮緩存問題呢?一般微服務架構設計時,分佈式應用可能導致髒數據,特別是在秒殺情況下需要特別注意(可以接入外部二級緩存)

簡介

在這裏插入圖片描述文檔中已經很明顯說明了什麼cashe,什麼是一級緩存和二次緩存。個人就總結爲:緩存即相同查詢時,在沒有發生更新、提交、回滾和關閉時,多次查詢不會從數據庫中返回,直接從Cashe的緩存實現類中返回。一級緩存居於sqlsesson緩存,二級緩存是居於mapper級別緩存,啓用一級緩存的方式是通過設置localCacheScope=STATEMENT,啓用二級緩存的方式是總配置cacheEnabled爲true和mapper文件配置cache

源碼解讀

  1. 在構建sqlsessionFactory時,構建Executor,具體構建方法是DefaultSqlSessionFactory#openSessionFromConnection,我們更多關注裏面的Configuration#newExecutor
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
   
    
    executorType = executorType == null ? defaultExecutorType : executorType; // 執行類型判斷,這裏不多說,下一節細談
    executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
    Executor executor;
    if (ExecutorType.BATCH == executorType) {
   
    
      executor = new BatchExecutor(this, transaction);
    } else if (ExecutorType.REUSE == executorType) {
   
    
      executor = new ReuseExecutor(this, transaction);
    } else {
   
    
      executor = new SimpleExecutor(this, transaction);
    }
    if (cacheEnabled) {
   
     // 這裏默認啓用CachingExecutor動態代理
      executor = new CachingExecutor(executor);
    }
    executor = (Executor) interceptorChain.pluginAll(executor); // 執行攔截器
    return executor;
  }
  1. 從上面可知我們更多關注的對象是CachingExecutor,下面我們看CachingExecutor來逆推到底如何實現二級緩存。
@Override
  public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
      throws SQLException {
   
    
    Cache cache = ms.getCache(); // 這裏是我們關注的二級緩存對象,此對象是從MappedStatement中獲取
    if (cache != null) {
   
    
      flushCacheIfRequired(ms); // 這裏判斷是否有此緩存和是否需要清空緩存,若沒有則存放在TransactionalCacheManager#transactionalCaches中
      if (ms.isUseCache() && resultHandler == null) {
   
     // 判斷是否有Cache啓動
        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); // 不啓用緩存則查詢本地緩存
  }
  1. 從上面緩存執行代碼中,我們可以查看到外部緩存時從MappedStatement中獲取,接下來我們看這個緩存如何構造,查看源碼可知MappedStatement#cache加入了緩存,繼續跟進,則MapperBuilderAssistant#addMappedStatement中加入,再往上一層則MapperBuilderAssistant#useNewCache構建。接下來我們看看構建方法
public Cache useNewCache(Class<? extends Cache> typeClass,
      Class<? extends Cache> evictionClass,
      Long flushInterval,
      Integer size,
      boolean readWrite,
      boolean blocking,
      Properties props) {
   
    
    Cache cache = new CacheBuilder(currentNamespace) // 命名空間來構建
        .implementation(valueOrDefault(typeClass, PerpetualCache.class)) // 默認PerpetualCache緩存,當然這裏不支持分佈式
        .addDecorator(valueOrDefault(evictionClass, LruCache.class))
        .clearInterval(flushInterval)
        .size(size)
        .readWrite(readWrite)
        .blocking(blocking)
        .properties(props)
        .build();
    configuration.addCache(cache);
    currentCache = cache;
    return cache;
  }

此時的Cache的屬性Id爲currentNamespace和MappedStatement同時構建,再往上一層發現XMLMapperBuilder#cacheElement方法,最後configurationElement中元素cach判斷是否構建。最後真相大白,實際上二級緩存是居於mapper來構建的。XMLMapperBuilder解析xml文件 -》XMLMapperBuilder#cacheElement欲準備構建-》MapperBuilderAssistant#useNewCache構建cache -》MapperBuilderAssistant#addMappedStatement構建MappedStatement時傳入Cache。其實最終發現解讀mybatis源碼並不難。
4. 我們發現cache是居於mappedStatement構建的,接下來回歸CachingExecutor如何執行Cache。在講解之前,我們可以看看CacheKey的組成(BaseExecutor#createCacheKey),這裏不細談,主要知道Cache的key通過namespace和sql以及查詢條件組成即可。我們來談談如何獲取Cache,我們返回上面步驟2中的tcm.getObject(cache, key)。下面我們詳細看看裏面方法.

private final Map<Cache, TransactionalCache> transactionalCaches = new HashMap<>();
private TransactionalCache getTransactionalCache(Cache cache) {
   
    
    return transactionalCaches.computeIfAbsent(cache, TransactionalCache::new);
  }

發現每次Cache還有重複利用哦,交給TransactionalCacheManager管理。
5. 當二級緩存查找不到時,執行executor,如上面2的delegate.query。
6. 執行查詢時,執行BaseExecutor#query,我們來看看sql的查詢方法:

@SuppressWarnings("unchecked")
  @Override
  public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
   
    
    // 省略
    List<E> list;
    try {
   
    
      queryStack++;
      // 發現這裏是查詢localCache類型,若查不到纔去數據庫中查詢,new PerpetualCache("LocalOutputParameterCache")
      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();
      // 緩存對象,判斷是否是STATEMENT,若是,則清空LocalCache
      if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
   
    
        // issue #482
        clearLocalCache();
      }
    }
    return list;
  }

接下來真相大白所謂的二級緩存就是額外增加的外部緩存,實際一級緩存也存在;先判斷是否開啓二級緩存,若沒有開啓則查詢localCache,若開啓則查詢二級緩存並維護,沒有查到時則查詢一級緩存,若一級緩存也查不到則查詢數據庫。

緩存使用

1、默認二級緩存不開啓。若想禁用一級緩存,則可以設置localCacheScope=STATEMENT,如下:

<settings>
   <setting name="localCacheScope" value="STATEMENT"/>
</settings>

2、若想指定sql語句清空緩存,則通過mapper中設置useCache="false"和flushCache=“true”(線程不安全,這裏不詳細講解),如下:

<select id="selectTest" resultMap="userTable" useCache="false" flushCache="true" >
        select * from user
    </select>

具體原理實現代碼flushCache如CachingExecutor#query會刷新緩存
3、調用EhcacheCache緩存
< cache type=“org.mybatis.caches.ehcache.EhcacheCache”/>

總結&反思

  1. 緩存的概念和一級緩存以及二級緩存的概念
  2. 緩存的使用場景和考慮的問題
  3. 解讀源碼的思路,若有更好的方式,請多多指教
  4. 幾個關鍵類和接口瞭解:DefaultSqlSessionFactory、Configuration、CachingExecutor、MappedStatement、MapperBuilderAssistant、XMLMapperBuilder、Cache、CacheKey

結合上一篇講解mybatis-plugin,是否覺得mybatis是個很簡單的框架呢?哈哈哈,個人感覺方法只有一個,如何使用方法就是難點,巧妙和熟練使用方法,在遇到相應技術點時就考慮這種類似的問題,其實mybatis是半ORM框架,因爲依然存在sql,mybatis強大之處在於強大的動態sql和動態代理模式的設計理念。接下來一篇個人來探討如何搭建一個分佈式架構的理念!請稍等!

零散關注背誦點記錄

數據庫特點:
1、數據結構化 2、數據的共享性高,冗餘低,易擴展 3、數據獨立性高 4、統一管理和控制
事務的特性:原子性(Atomicity)、一致性(Consistency)、隔離性(Isolation)、持久性(Durability)
概念性理解:髒讀、不可重複讀、幻讀

top指令:rownum和rowid、limit


NOT NULL
UNIQUE
PRIMARY KEY
FOREIGN KEY
CHECK
DEFAULT

-- 創建唯一索引
CREATE UNIQUE INDEX uk_users_name ON t_users(name); 
-- 刪除唯一索引
drop index uk_users_name;
-- 創建唯一鍵約束
ALTER TABLE t_users ADD CONSTRAINT uk_users_name1 UNIQUE (NAME);
ALTER TABLE Orders ADD CONSTRAINT fk_PerOrders FOREIGN KEY (Id_P) REFERENCES Persons(Id_P)

alter table dept2 add primary key(dname);
drop index index_name;


 -- 創建序列  Student_stuId_Seq --
create sequence mySeq    :序列名
start with 1          :序列開始值
increment by 1                   :序列每次自增1
maxvalue 3                        :序列最大值
minvalue 1                         :序列最小值
cache 2                              :每次緩存的數值個數
cycle;                                 :是否循環[cycle:循環   |   nocycle: 不循環]


JdbcTransactionFactory   TransactionIsolationLevel(NONE,Read_uncommit,Read_commit,Reapeatable_read,serializable)
BaseExecutor事務管理JdbcTransaction實現Proxy動態代理(InvocationHandler)

1、掃描xml或者註解文件,
properties
settings(defaultExecutorType,logImpl)
typeAliases
Plugins(interceptor並實力話)
objectFactory
objectWrapperFactory
reflectorFactory
environments(創建TransactionFactory,datasource)
databaseIdProvider
typeHandlers
mappers
2、創建SqlSessionFactory
3、openSession打開sql緩存執行器時 DefaultSqlSessionFactory
	1、創建事務工廠(TransationFactory)
	2、ManagedTransaction獲取事務管理器,datasource和connection
	3、獲取數據庫執行類ExecutorType判斷,SimpleExecutor、ReuseExecutor、BatchExecutor還是
	CachingExecutor(cacheEnabled)這裏默認開啓一級緩存,同一個sqlSession多次時只執行一次。
	4、創建DefaultSqlSession
DefaultSqlSession-》CachingExecutor-》BaseExecutor-》 flushCacheIfRequired(ms)

4、創建statementMap,查看是否有sql一樣,如果有看是否關閉,若關閉,則jdbcTransaction中通過datasource獲取連接
這裏jdbcTransation管理事務

typeAlias 類型聲明,簡寫之類
TypeHandler類型轉換,jdbc裏類型轉換成java類型數據。
defaultObjectFactory主要構造bean,返回結果集反射之類的和xml解析

Plugin是居於代理模式啓動interceptors,具體流程如下:
	1、configuration#addInterceptor解析文件存放所有攔截器,configuration#newExecutor調用代理
	2、interceptorChain.pluginAll啓動代理,返回Executor
	3、interceptor.plugin調用Plugin.wrap,plugin創建InvocationHandler代理對象,封裝的所有方法攔截器
	在對於執行類的所有方法Map<Class,Set<Method>>中。
	4、當代理接口類執行時,調用先獲取method.getDeclaringClass()對於的聲明類,然後取出攔截器的方法(Map<Class,Set<Method>>)
	判斷調用方法是否相同if (methods != null && methods.contains(method)),若相同,則執行攔截器
攔截器基本攔截方式:Parameterhandler、ResultSetHandler、StatementHandler、Executor


引用別人面試技術篇:https://www.cnblogs.com/qmillet/p/12523636.html





發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章