欧美一区二区三区老妇人-欧美做爰猛烈大尺度电-99久久夜色精品国产亚洲a-亚洲福利视频一区二区

使用MyBatis如何實現(xiàn)一級緩存與二級緩存

這期內(nèi)容當(dāng)中小編將會給大家?guī)碛嘘P(guān)使用MyBatis如何實現(xiàn)一級緩存與二級緩存,文章內(nèi)容豐富且以專業(yè)的角度為大家分析和敘述,閱讀完這篇文章希望大家可以有所收獲。

臨潁ssl適用于網(wǎng)站、小程序/APP、API接口等需要進行數(shù)據(jù)傳輸應(yīng)用場景,ssl證書未來市場廣闊!成為成都創(chuàng)新互聯(lián)的ssl證書銷售渠道,可以享受市場價格4-6折優(yōu)惠!如果有意向歡迎電話聯(lián)系或者加微信:028-86922220(備注:SSL證書合作)期待與您的合作!

MyBatis緩存

我們知道,頻繁的數(shù)據(jù)庫操作是非常耗費性能的(主要是因為對于DB而言,數(shù)據(jù)是持久化在磁盤中的,因此查詢操作需要通過IO,IO操作速度相比內(nèi)存操作速度慢了好幾個量級),尤其是對于一些相同的查詢語句,完全可以把查詢結(jié)果存儲起來,下次查詢同樣的內(nèi)容的時候直接從內(nèi)存中獲取數(shù)據(jù)即可,這樣在某些場景下可以大大提升查詢效率。

MyBatis的緩存分為兩種:

一級緩存,一級緩存是SqlSession級別的緩存,對于相同的查詢,會從緩存中返回結(jié)果而不是查詢數(shù)據(jù)庫

二級緩存,二級緩存是Mapper級別的緩存,定義在Mapper文件的<cache>標(biāo)簽中并需要開啟此緩存,多個Mapper文件可以共用一個緩存,依賴<cache-ref>標(biāo)簽配置

下面來詳細(xì)看一下MyBatis的一二級緩存。

MyBatis一級緩存工作流程

接著看一下MyBatis一級緩存工作流程。前面說了,MyBatis的一級緩存是SqlSession級別的緩存,當(dāng)openSession()的方法運行完畢或者主動調(diào)用了SqlSession的close方法,SqlSession就被回收了,一級緩存與此同時也一起被回收掉了。前面的文章有說過,在MyBatis中,無論selectOne還是selectList方法,最終都被轉(zhuǎn)換為了selectList方法來執(zhí)行,那么看一下SqlSession的selectList方法的實現(xiàn):

public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
  try {
   MappedStatement ms = configuration.getMappedStatement(statement);
   return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
  } catch (Exception e) {
   throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e);
  } finally {
   ErrorContext.instance().reset();
  }
}

繼續(xù)跟蹤第4行的代碼,到BaseExeccutor的query方法:

 public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
   BoundSql boundSql = ms.getBoundSql(parameter);
   CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
   return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
 }

 第3行構(gòu)建緩存條件CacheKey,這里涉及到怎么樣條件算是和上一次查詢是同一個條件的一個問題,因為同一個條件就可以返回上一次的結(jié)果回去,這部分代碼留在下一部分分析。

接著看第4行的query方法的實現(xiàn),代碼位于CachingExecutor中:

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) {
   flushCacheIfRequired(ms);
   if (ms.isUseCache() && resultHandler == null) {
    ensureNoOutParams(ms, parameterObject, boundSql);
    @SuppressWarnings("unchecked")
    List<E> list = (List<E>) tcm.getObject(cache, key);
    if (list == null) {
     list = delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
     tcm.putObject(cache, key, list); // issue #578 and #116
    }
    return list;
   }
  }
  return delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}

第3行~第16行的代碼先不管,繼續(xù)跟第17行的query方法,代碼位于BaseExecutor中:

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 &#63; (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--;
  }
  ...
}

看12行,query的時候會嘗試從localCache中去獲取查詢結(jié)果,如果獲取到的查詢結(jié)果為null,那么執(zhí)行16行的代碼從DB中撈數(shù)據(jù),撈完之后會把CacheKey作為key,把查詢結(jié)果作為value放到localCache中。

MyBatis一級緩存存儲流程看完了,接著我們從這段代碼中可以得到三個結(jié)論:

MyBatis的一級緩存是SqlSession級別的,但是它并不定義在SqlSessio接口的實現(xiàn)類DefaultSqlSession中,而是定義在DefaultSqlSession的成員變量Executor中,Executor是在openSession的時候被實例化出來的,它的默認(rèn)實現(xiàn)為SimpleExecutor

MyBatis中的一級緩存,與有沒有配置無關(guān),只要SqlSession存在,MyBastis一級緩存就存在,localCache的類型是PerpetualCache,它其實很簡單,一個id屬性+一個HashMap屬性而已,id是一個名為"localCache"的字符串,HashMap用于存儲數(shù)據(jù),Key為CacheKey,Value為查詢結(jié)果

MyBatis的一級緩存查詢的時候默認(rèn)都是會先嘗試從一級緩存中獲取數(shù)據(jù)的,但是我們看第6行的代碼做了一個判斷,ms.isFlushCacheRequired(),即想每次查詢都走DB也行,將<select>標(biāo)簽中的flushCache屬性設(shè)置為true即可,這意味著每次查詢的時候都會清理一遍PerpetualCache,PerpetualCache中沒數(shù)據(jù),自然只能走DB

從MyBatis一級緩存來看,它以單純的HashMap做緩存,沒有容量控制,而一次SqlSession中通常來說并不會有大量的查詢操作,因此只適用于一次SqlSession,如果用到二級緩存的Mapper級別的場景,有可能緩存數(shù)據(jù)不斷碰到而導(dǎo)致內(nèi)存溢出。

還有一點,差點忘了寫了,<insert>、<delete>、<update>最終都會轉(zhuǎn)換為update方法,看一下BaseExecutor的update方法:

public int update(MappedStatement ms, Object parameter) throws SQLException {
  ErrorContext.instance().resource(ms.getResource()).activity("executing an update").object(ms.getId());
  if (closed) {
   throw new ExecutorException("Executor was closed.");
  }
  clearLocalCache();
  return doUpdate(ms, parameter);
}

第6行clearLocalCache()方法,這意味著所有的增、刪、改都會清空本地緩存,這和是否配置了flushCache=true是無關(guān)的。

這很好理解,因為增、刪、改這三種操作都可能會導(dǎo)致查詢出來的結(jié)果并不是原來的結(jié)果,如果增、刪、改不清理緩存,那么可能導(dǎo)致讀取出來的數(shù)據(jù)是臟數(shù)據(jù)。

一級緩存的CacheKey

接著我們看下一個問題:怎么樣的查詢條件算和上一次查詢是一樣的查詢,從而返回同樣的結(jié)果回去?這個問題,得從CacheKey說起。

我們先看一下CacheKey的數(shù)據(jù)結(jié)構(gòu):

public class CacheKey implements Cloneable, Serializable {
 private static final long serialVersionUID = 1146682552656046210L;
 public static final CacheKey NULL_CACHE_KEY = new NullCacheKey();
 private static final int DEFAULT_MULTIPLYER = 37;
 private static final int DEFAULT_HASHCODE = 17;
 private int multiplier;
 private int hashcode;
 private long checksum;
 private int count;
 private List<Object> updateList;
 ...
}

其中最重要的是第14行的updateList這個兩個屬性,為什么這么說,因為HashMap的Key是CacheKey,而HashMap的get方法是先判斷hashCode,在hashCode沖突的情況下再進行equals判斷,因此最終無論如何都會進行一次equals的判斷,看下equals方法的實現(xiàn):

public boolean equals(Object object) {
  if (this == object) {
   return true;
  }
  if (!(object instanceof CacheKey)) {
   return false;
  }
  final CacheKey cacheKey = (CacheKey) object;
  if (hashcode != cacheKey.hashcode) {
   return false;
  }
  if (checksum != cacheKey.checksum) {
   return false;
  }
  if (count != cacheKey.count) {
   return false;
  }
  for (int i = 0; i < updateList.size(); i++) {
   Object thisObject = updateList.get(i);
   Object thatObject = cacheKey.updateList.get(i);
   if (thisObject == null) {
    if (thatObject != null) {
     return false;
    }
   } else {
    if (!thisObject.equals(thatObject)) {
     return false;
    }
   }
  }
  return true;
}

看到整個方法的流程都是圍繞著updateList中的每個屬性進行逐一比較,因此再進一步的,我們要看一下updateList中到底存儲了什么。

關(guān)于updateList里面存儲的數(shù)據(jù)我們可以看下哪里使用了updateList的add方法,然后一步一步反推回去即可。updateList中數(shù)據(jù)的添加是在doUpdate方法中:

private void doUpdate(Object object) {
  int baseHashCode = object == null &#63; 1 : object.hashCode();
  count++;
  checksum += baseHashCode;
  baseHashCode *= count;
  hashcode = multiplier * hashcode + baseHashCode;
  updateList.add(object);
}

它的調(diào)用方為update方法:

public void update(Object object) {
  if (object != null && object.getClass().isArray()) {
   int length = Array.getLength(object);
   for (int i = 0; i < length; i++) {
    Object element = Array.get(object, i);
    doUpdate(element);
   }
  } else {
   doUpdate(object);
  }
}

這里主要是對輸入?yún)?shù)是數(shù)組類型進行了一次判斷,是數(shù)組就遍歷逐一做doUpdate,否則就直接做doUpdate。再看update方法的調(diào)用方,其實update方法的調(diào)用方有挺多處,但是這里我們要看的是Executor中的,看一下BaseExecutor中的createCacheKey方法實現(xiàn):

...
CacheKey cacheKey = new CacheKey();
cacheKey.update(ms.getId());
cacheKey.update(rowBounds.getOffset());
cacheKey.update(rowBounds.getLimit());
cacheKey.update(boundSql.getSql());
...

到了這里應(yīng)當(dāng)一目了然了,MyBastis從三個維度判斷兩次查詢是相同的:

<select>標(biāo)簽所在的Mapper的Namespace+<select>標(biāo)簽的id屬性

RowBounds的offset和limit屬性,RowBounds是MyBatis用于處理分頁的一個類,offset默認(rèn)為0,limit默認(rèn)為Integer.MAX_VALUE

<select>標(biāo)簽中定義的sql語句

即只要兩次查詢滿足以上三個條件且沒有定義flushCache="true",那么第二次查詢會直接從MyBatis一級緩存PerpetualCache中返回數(shù)據(jù),而不會走DB。

MyBatis二級緩存

上面說完了MyBatis,接著看一下MyBatis二級緩存,還是從二級緩存工作流程開始。還是從DefaultSqlSession的selectList方法進去:

public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
  try {
   MappedStatement ms = configuration.getMappedStatement(statement);
   return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
  } catch (Exception e) {
   throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e);
  } finally {
   ErrorContext.instance().reset();
  }
}

執(zhí)行query方法,方法位于CachingExecutor中:

 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);
   return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
 }

繼續(xù)跟第4行的query方法,同樣位于CachingExecutor中:

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) {
   flushCacheIfRequired(ms);
   if (ms.isUseCache() && resultHandler == null) {
    ensureNoOutParams(ms, parameterObject, boundSql);
    @SuppressWarnings("unchecked")
    List<E> list = (List<E>) tcm.getObject(cache, key);
    if (list == null) {
     list = delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
     tcm.putObject(cache, key, list); // issue #578 and #116
    }
    return list;
   }
  }
  return delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}

從這里看到,執(zhí)行第17行的BaseExecutor的query方法之前,會先拿Mybatis二級緩存,而BaseExecutor的query方法會優(yōu)先讀取MyBatis一級緩存,由此可以得出一個重要結(jié)論:假如定義了MyBatis二級緩存,那么MyBatis二級緩存讀取優(yōu)先級高于MyBatis一級緩存。

而第3行~第16行的邏輯:

第5行的方法很好理解,根據(jù)flushCache=true或者flushCache=false判斷是否要清理二級緩存

第7行的方法是保證MyBatis二級緩存不會存儲存儲過程的結(jié)果

第9行的方法先嘗試從tcm中獲取查詢結(jié)果,這個tcm解釋一下,這又是一個裝飾器模式(數(shù)數(shù)MyBatis用到了多少裝飾器模式了),創(chuàng)建一個事物緩存TranactionalCache,持有Cache接口,Cache接口的實現(xiàn)類就是根據(jù)我們在Mapper文件中配置的<cache>創(chuàng)建的Cache實例

第10行~第12行,如果沒有從MyBatis二級緩存中拿到數(shù)據(jù),那么就會查一次數(shù)據(jù)庫,然后放到MyBatis二級緩存中去
至于如何判定上次查詢和這次查詢是一次查詢?由于這里的CacheKey和MyBatis一級緩存使用的是同一個CacheKey,因此它的判定條件和前文寫過的MyBatis一級緩存三個維度的判定條件是一致的。

最后再來談一點,"Cache cache = ms.getCache()"這句代碼十分重要,這意味著Cache是從MappedStatement中獲取到的,而MappedStatement又和每一個<insert>、<delete>、<update>、<select>綁定并在MyBatis啟動的時候存入Configuration中:

protected final Map<String, MappedStatement> mappedStatements = new StrictMap<MappedStatement>("Mapped Statements collection");

因此MyBatis二級緩存的生命周期即整個應(yīng)用的生命周期,應(yīng)用不結(jié)束,定義的二級緩存都會存在在內(nèi)存中。

從這個角度考慮,為了避免MyBatis二級緩存中數(shù)據(jù)量過大導(dǎo)致內(nèi)存溢出,MyBatis在配置文件中給我們增加了很多配置例如size(緩存大?。lushInterval(緩存清理時間間隔)、eviction(數(shù)據(jù)淘汰算法)來保證緩存中存儲的數(shù)據(jù)不至于太過龐大。

MyBatis二級緩存實例化過程

接著看一下MyBatis二級緩存<cache>實例化的過程,代碼位于XmlMapperBuilder的cacheElement方法中:

private void cacheElement(XNode context) throws Exception {
  if (context != null) {
   String type = context.getStringAttribute("type", "PERPETUAL");
   Class<&#63; extends Cache> typeClass = typeAliasRegistry.resolveAlias(type);
   String eviction = context.getStringAttribute("eviction", "LRU");
   Class<&#63; 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);
  }
}

這里分別取<cache>中配置的各個屬性,關(guān)注一下兩個默認(rèn)值:

type表示緩存實現(xiàn),默認(rèn)是PERPETUAL,根據(jù)typeAliasRegistry中注冊的,PERPETUAL實際對應(yīng)PerpetualCache,這和MyBatis一級緩存是一致的

eviction表示淘汰算法,默認(rèn)是LRU算法

第3行~第11行拿到了所有屬性,那么調(diào)用12行的useNewCache方法創(chuàng)建緩存:

public Cache useNewCache(Class<&#63; extends Cache> typeClass,
   Class<&#63; 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);
  currentCache = cache;
  return cache;
}

這里又使用了建造者模式,跟一下第16行的build()方法,在此之前該傳入的參數(shù)都已經(jīng)傳入了CacheBuilder:

public Cache build() {
  setDefaultImplementations();
  Cache cache = newBaseCacheInstance(implementation, id);
  setCacheProperties(cache);
  // issue #352, do not apply decorators to custom caches
  if (PerpetualCache.class.equals(cache.getClass())) {
   for (Class<&#63; extends Cache> decorator : decorators) {
    cache = newCacheDecoratorInstance(decorator, cache);
    setCacheProperties(cache);
   }
   cache = setStandardDecorators(cache);
  } else if (!LoggingCache.class.isAssignableFrom(cache.getClass())) {
   cache = new LoggingCache(cache);
  }
  return cache;
}

第3行的代碼,構(gòu)建基礎(chǔ)的緩存,implementation指的是type配置的值,這里是默認(rèn)的PerpetualCache。

第6行的代碼,如果是PerpetualCache,那么繼續(xù)裝飾(又是裝飾器模式,可以數(shù)數(shù)這幾篇MyBatis源碼解析的文章里面出現(xiàn)了多少次裝飾器模式了),這里的裝飾是根據(jù)eviction進行裝飾,到這一步,給PerpetualCache加上了LRU的功能。

第11行的代碼,繼續(xù)裝飾,這次MyBatis將它命名為標(biāo)準(zhǔn)裝飾,setStandardDecorators方法實現(xiàn)為:

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);
  }
}

這次是根據(jù)其它的配置參數(shù)來:

  • 如果配置了flushInterval,那么繼續(xù)裝飾為ScheduledCache,這意味著在調(diào)用Cache的getSize、putObject、getObject、removeObject四個方法的時候都會進行一次時間判斷,如果到了指定的清理緩存時間間隔,那么就會將當(dāng)前緩存清空
  • 如果readWrite=true,那么繼續(xù)裝飾為SerializedCache,這意味著緩存中所有存儲的內(nèi)存都必須實現(xiàn)Serializable接口
  • 跟配置無關(guān),將之前裝飾好的Cache繼續(xù)裝飾為LoggingCache與SynchronizedCache,前者在getObject的時候會打印緩存命中率,后者將Cache接口中所有的方法都加了Synchronized關(guān)鍵字進行了同步處理
  • 如果blocking=true,那么繼續(xù)裝飾為BlockingCache,這意味著針對同一個CacheKey,拿數(shù)據(jù)與放數(shù)據(jù)、刪數(shù)據(jù)是互斥的,即拿數(shù)據(jù)的時候必須沒有在放數(shù)據(jù)、刪數(shù)據(jù)
     

Cache全部裝飾完畢,返回,至此MyBatis二級緩存生成完畢。

最后說一下,MyBatis支持三種類型的二級緩存:

  • MyBatis默認(rèn)的緩存,type為空,Cache為PerpetualCache
  • 自定義緩存
  • 第三方緩存

從build()方法來看,后兩種場景的Cache,MyBatis只會將其裝飾為LoggingCache,理由很簡單,這些緩存的定期清除功能、淘汰過期數(shù)據(jù)功能開發(fā)者自己或者第三方緩存都已經(jīng)實現(xiàn)好了,根本不需要依賴MyBatis本身的裝飾。

上述就是小編為大家分享的使用MyBatis如何實現(xiàn)一級緩存與二級緩存了,如果剛好有類似的疑惑,不妨參照上述分析進行理解。如果想知道更多相關(guān)知識,歡迎關(guān)注創(chuàng)新互聯(lián)行業(yè)資訊頻道。

當(dāng)前名稱:使用MyBatis如何實現(xiàn)一級緩存與二級緩存
文章地址:http://chinadenli.net/article12/pijjdc.html

成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供用戶體驗、Google品牌網(wǎng)站建設(shè)、搜索引擎優(yōu)化ChatGPT、網(wǎng)頁設(shè)計公司

廣告

聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請盡快告知,我們將會在第一時間刪除。文章觀點不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時需注明來源: 創(chuàng)新互聯(lián)

成都seo排名網(wǎng)站優(yōu)化