首頁 > 軟體

詳解Mybatis的快取

2021-01-16 12:00:15

Mybatis的快取

mybatis是一個查詢資料庫的封裝框架,主要是封裝提供靈活的增刪改sql,開發中,service層能夠通過mybatis元件查詢和修改資料庫中表的資料;作為查詢工具,mybatis有使用快取,這裡講一下mybatis的快取相關原始碼。

快取

在計算機裡面,任何資訊都有源頭,快取一般指源頭資訊讀取後,放在記憶體或者其他讀取較快的地方,下次讀取相同資訊不去源頭查詢而是直接從記憶體(或者能快速存取的硬體)讀取。這樣可以減少硬體使用,提高讀取速度。

mybatis也是這樣,查詢資料庫的資料之後,mybatis可以把查詢結果快取到記憶體,下次查詢如果查詢語句相同,並且查詢相關的表的資料沒被修改過,就可以直接返回快取中的結果,而不用去查詢資料庫的語句,有效節省了時間。

簡單看一下mybatis一級快取和二級快取相關原始碼,學習使用

一級快取

通過檢視原始碼可知,一級快取是繫結sqSsession中的,所以每次查詢sqlSession不同就失效,相同的sqlSession可以使用一級快取。

mybatis預設sqlsession:org.apache.ibatis.session.defaults.DefaultSqlSession

構造方法中傳入executor(查詢執行物件)

 public DefaultSqlSession(Configuration configuration, Executor executor, boolean autoCommit) {
  this.configuration = configuration;
  this.executor = executor;
  this.dirty = false;
  this.autoCommit = autoCommit;
 }

executor中攜帶一級快取成員:

 protected BaseExecutor(Configuration configuration, Transaction transaction) {
  this.transaction = transaction;
  this.deferredLoads = new ConcurrentLinkedQueue<>();
  this.localCache = new PerpetualCache("LocalCache"); //預設一級快取
  this.localOutputParameterCache = new PerpetualCache("LocalOutputParameterCache");
  this.closed = false;
  this.configuration = configuration;
  this.wrapper = this;
 }

查詢使用一級快取邏輯

org.apache.ibatis.executor.BaseExecutor.query()

 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());
  
  List<E> list;
  try {
   queryStack++;
   	//localCache 一級快取
   list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
    //先從一級快取中獲取,key是通過sql語句生成
   if (list != null) {
    handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
   } else {
    // 如果快取中沒有 才從資料庫查詢
    list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
   }
  } finally {
   queryStack--;
  }
  return list;
 }

 //從資料庫讀取資料
 private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
  List<E> list;
  localCache.putObject(key, EXECUTION_PLACEHOLDER);
  try {
   list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
  } finally {
   localCache.removeObject(key);//將一級快取清除
  }
  localCache.putObject(key, list);//返回查詢結果之前,先放入一級快取 重新整理
  if (ms.getStatementType() == StatementType.CALLABLE) {
   localOutputParameterCache.putObject(key, parameter);
  }
  return list;
 }

二級快取

二級快取mapper中的,預設是開啟的,但需要在對映檔案mapper.xml中新增<cache/>標籤

<mapper namespace="userMapper">
	<cache/><!-- 新增cache標籤表示此mapper使用二級快取 -->
</mapper>

設定false可以關閉二級快取

二級快取的解析

org.apache.ibatis.builder.xml.XMLMapperBuilder

 private void configurationElement(XNode context) {
  try {
   //...
   cacheElement(context.evalNode("cache")); //解析cache標籤
  } catch (Exception e) {
   throw new BuilderException("Error parsing Mapper XML. The XML location is '" + resource + "'. Cause: " + e, e);
  }
 }

 private void cacheElement(XNode context) {
  if (context != null) { // if hava cache tag 如果有cache標籤才執行下面的邏輯
   String type = context.getStringAttribute("type", "PERPETUAL");
   Class<? extends Cache> typeClass = typeAliasRegistry.resolveAlias(type);
   String eviction = context.getStringAttribute("eviction", "LRU");
   Class<? extends Cache> evictionClass = typeAliasRegistry.resolveAlias(eviction);
   Long flushInterval = context.getLongAttribute("flushInterval");
   Integer size = context.getIntAttribute("size");
   boolean readWrite = !context.getBooleanAttribute("readOnly", false);
   boolean blocking = context.getBooleanAttribute("blocking", false);
   Properties props = context.getChildrenAsProperties();
   builderAssistant.useNewCache(typeClass, evictionClass, flushInterval, size, readWrite, blocking, props);//建立二級快取
  }
 }

org.apache.ibatis.builder.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))
    .addDecorator(valueOrDefault(evictionClass, LruCache.class))
    .clearInterval(flushInterval)
    .size(size)
    .readWrite(readWrite)
    .blocking(blocking)
    .properties(props)
    .build();
  configuration.addCache(cache);//二級快取賦值,如果cache標籤為空,不會執行此方法,currentCache為空
  currentCache = cache; 
  return cache;
 }

 在對映檔案mapper中如果沒有cache標籤,不會執行上面的useNewCache方法,cache為null,就不會使用二級快取(相當於失效)。

查詢使用二級快取邏輯

org.apache.ibatis.executor.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(); 
  if (cache != null) {//如果二級快取物件不為空 嘗試在二級快取中獲取(沒有cache標籤此物件就是空)
   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); //如果為空,使用delegate查詢(BaseExecutor)
     tcm.putObject(cache, key, list); // 查詢結果儲存到二級快取
    }
    return list;
   }
  }
  return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
 }

二級快取和一級快取不用想,資料庫的資料被修改是要清空快取的,不然資料有誤,至於怎麼清空,是另一套邏輯了,mapper中的cache標籤可以設定一些引數,比如快取定期清空。

一級二級快取先後順序

mybatis預設是先查詢二級快取,沒有,再檢視一級快取,都為空,最後查詢資料庫

以上就是詳解Mybatis的快取的詳細內容,更多關於Mybatis的快取的資料請關注it145.com其它相關文章!


IT145.com E-mail:sddin#qq.com