日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

MyBatis】MyBatis一级缓存和二级缓存

發(fā)布時間:2023/12/3 编程问答 39 豆豆
生活随笔 收集整理的這篇文章主要介紹了 MyBatis】MyBatis一级缓存和二级缓存 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

轉(zhuǎn)載自??MyBatis】MyBatis一級緩存和二級緩存

MyBatis自帶的緩存有一級緩存和二級緩存

一級緩存

Mybatis的一級緩存是指Session緩存。一級緩存的作用域默認(rèn)是一個SqlSession。Mybatis默認(rèn)開啟一級緩存。?
也就是在同一個SqlSession中,執(zhí)行相同的查詢SQL,第一次會去數(shù)據(jù)庫進(jìn)行查詢,并寫到緩存中;?
第二次以后是直接去緩存中取。?
當(dāng)執(zhí)行SQL查詢中間發(fā)生了增刪改的操作,MyBatis會把SqlSession的緩存清空。

一級緩存的范圍有SESSION和STATEMENT兩種,默認(rèn)是SESSION,如果不想使用一級緩存,可以把一級緩存的范圍指定為STATEMENT,這樣每次執(zhí)行完一個Mapper中的語句后都會將一級緩存清除。?
如果需要更改一級緩存的范圍,可以在Mybatis的配置文件中,在下通過localCacheScope指定。

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

建議不需要修改

需要注意的是?
當(dāng)Mybatis整合Spring后,直接通過Spring注入Mapper的形式,如果不是在同一個事務(wù)中每個Mapper的每次查詢操作都對應(yīng)一個全新的SqlSession實(shí)例,這個時候就不會有一級緩存的命中,但是在同一個事務(wù)中時共用的是同一個SqlSession。?
如有需要可以啟用二級緩存。

二級緩存

Mybatis的二級緩存是指mapper映射文件。二級緩存的作用域是同一個namespace下的mapper映射文件內(nèi)容,多個SqlSession共享。Mybatis需要手動設(shè)置啟動二級緩存。

二級緩存是默認(rèn)啟用的(要生效需要對每個Mapper進(jìn)行配置),如想取消,則可以通過Mybatis配置文件中的元素下的子元素來指定cacheEnabled為false。

<settings><setting name="cacheEnabled" value="false" /> </settings>

cacheEnabled默認(rèn)是啟用的,只有在該值為true的時候,底層使用的Executor才是支持二級緩存的CachingExecutor。具體可參考Mybatis的核心配置類org.apache.ibatis.session.Configuration的newExecutor方法實(shí)現(xiàn)。?
可以通過源碼看看

... ? ?public Executor newExecutor(Transaction transaction) { ? ? ? ?return this.newExecutor(transaction, this.defaultExecutorType);} ? ?public Executor newExecutor(Transaction transaction, ExecutorType executorType) {executorType = executorType == null ? this.defaultExecutorType : executorType;executorType = executorType == null ? ExecutorType.SIMPLE : executorType;Object 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 (this.cacheEnabled) {//設(shè)置為true才執(zhí)行的executor = new CachingExecutor((Executor)executor);}Executor executor = (Executor)this.interceptorChain.pluginAll(executor); ? ? ? ?return executor;} ...

要使用二級緩存除了上面一個配置外,我們還需要在我們每個DAO對應(yīng)的Mapper.xml文件中定義需要使用的cache

... <mapper namespace="...UserMapper"><cache/><!-- 加上該句即可,使用默認(rèn)配置、還有另外一種方式,在后面寫出 -->... </mapper>

具體可以看org.apache.ibatis.executor.CachingExecutor類的以下實(shí)現(xiàn)?
其中使用的cache就是我們在對應(yīng)的Mapper.xml中定義的cache。

? ?public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {BoundSql boundSql = ms.getBoundSql(parameterObject);CacheKey key = this.createCacheKey(ms, parameterObject, rowBounds, boundSql); ? ? ? ?return this.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);} ? ?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 ?this.flushCacheIfRequired(ms); ? ? ? ? ? ?if (ms.isUseCache() && resultHandler == null) {//第二個條件 需要當(dāng)前的查詢語句是配置了使用cache的,即下面源碼的useCache()是返回true的 ?默認(rèn)是truethis.ensureNoOutParams(ms, parameterObject, boundSql);List<E> list = (List)this.tcm.getObject(cache, key); ? ? ? ? ? ? ? ?if (list == null) {list = this.delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql); ? ? ? ? ? ? ? ? ? ?this.tcm.putObject(cache, key, list);} ? ? ? ? ? ? ? ?return list;}} ? ? ? ?return this.delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);}

還有一個條件就是需要當(dāng)前的查詢語句是配置了使用cache的,即上面源碼的useCache()是返回true的,默認(rèn)情況下所有select語句的useCache都是true,如果我們在啟用了二級緩存后,有某個查詢語句是我們不想緩存的,則可以通過指定其useCache為false來達(dá)到對應(yīng)的效果。?
如果我們不想該語句緩存,可使用useCache=”false

<select id="selectByPrimaryKey" resultMap="BaseResultMap" parameterType="java.lang.String" useCache="false">select<include refid="Base_Column_List"/>from tuserwhere id = #{id,jdbcType=VARCHAR}</select>

cache定義的兩種使用方式

上面說了要想使用二級緩存,需要在每個DAO對應(yīng)的Mapper.xml文件中定義其中的查詢語句需要使用cache來緩存數(shù)據(jù)的。?
這有兩種方式可以定義,一種是通過cache元素定義,一種是通過cache-ref元素來定義。?
需要注意的是?
對于同一個Mapper來講,只能使用一個Cache,當(dāng)同時使用了和時,定義的優(yōu)先級更高(后面的代碼會給出原因)。?
Mapper使用的Cache是與我們的Mapper對應(yīng)的namespace綁定的,一個namespace最多只會有一個Cache與其綁定。

cache元素定義

使用cache元素來定義使用的Cache時,最簡單的做法是直接在對應(yīng)的Mapper.xml文件中指定一個空的元素(看前面的代碼),這個時候Mybatis會按照默認(rèn)配置創(chuàng)建一個Cache對象,準(zhǔn)備的說是PerpetualCache對象,更準(zhǔn)確的說是LruCache對象(底層用了裝飾器模式)。?
具體的可看org.apache.ibatis.builder.xml.XMLMapperBuilder中的cacheElement()方法解析cache元素的邏輯。

... ? ?private void configurationElement(XNode context) { ? ? ? ?try {String namespace = context.getStringAttribute("namespace"); ? ? ? ? ? ?if (namespace.equals("")) { ? ? ? ? ? ? ? ?throw new BuilderException("Mapper's namespace cannot be empty");} else { ? ? ? ? ? ? ? ?this.builderAssistant.setCurrentNamespace(namespace); ? ? ? ? ? ? ? ?this.cacheRefElement(context.evalNode("cache-ref")); ? ? ? ? ? ? ? ?this.cacheElement(context.evalNode("cache"));//執(zhí)行在后面this.parameterMapElement(context.evalNodes("/mapper/parameterMap")); ? ? ? ? ? ? ? ?this.resultMapElements(context.evalNodes("/mapper/resultMap")); ? ? ? ? ? ? ? ?this.sqlElement(context.evalNodes("/mapper/sql")); ? ? ? ? ? ? ? ?this.buildStatementFromContext(context.evalNodes("select|insert|update|delete"));}} catch (Exception var3) { ? ? ? ? ? ?throw new BuilderException("Error parsing Mapper XML. Cause: " + var3, var3);}} ... ? ?private void cacheRefElement(XNode context) { ? ? ? ?if (context != null) { ? ? ? ? ? ?this.configuration.addCacheRef(this.builderAssistant.getCurrentNamespace(), context.getStringAttribute("namespace"));CacheRefResolver cacheRefResolver = new CacheRefResolver(this.builderAssistant, context.getStringAttribute("namespace")); ? ? ? ? ? ?try {cacheRefResolver.resolveCacheRef();} catch (IncompleteElementException var4) { ? ? ? ? ? ? ? ?this.configuration.addIncompleteCacheRef(cacheRefResolver);}}} ? ?private void cacheElement(XNode context) throws Exception { ? ? ? ?if (context != null) {String type = context.getStringAttribute("type", "PERPETUAL");Class<? extends Cache> typeClass = this.typeAliasRegistry.resolveAlias(type);String eviction = context.getStringAttribute("eviction", "LRU");Class<? extends Cache> evictionClass = this.typeAliasRegistry.resolveAlias(eviction);Long flushInterval = context.getLongAttribute("flushInterval");Integer size = context.getIntAttribute("size"); ? ? ? ? ? ?boolean readWrite = !context.getBooleanAttribute("readOnly", false).booleanValue();Properties props = context.getChildrenAsProperties(); ? ? ? ? ? ?this.builderAssistant.useNewCache(typeClass, evictionClass, flushInterval, size, readWrite, props);//如果同時存在<cache>和<cache-ref>,這里的設(shè)置會覆蓋前面的cache-ref的緩存}}

空cache元素定義會生成一個采用最近最少使用算法最多只能存儲1024個元素的緩存,而且是可讀寫的緩存,即該緩存是全局共享的,任何一個線程在拿到緩存結(jié)果后對數(shù)據(jù)的修改都將影響其它線程獲取的緩存結(jié)果,因?yàn)樗鼈兪枪蚕淼?#xff0c;同一個對象。

cache元素可指定如下屬性,每種屬性的指定都是針對都是針對底層Cache的一種裝飾,采用的是裝飾器的模式。

  • blocking:默認(rèn)為false,當(dāng)指定為true時將采用BlockingCache進(jìn)行封裝,blocking,阻塞的意思,使用BlockingCache會在查詢緩存時鎖住對應(yīng)的Key,如果緩存命中了則會釋放對應(yīng)的鎖,否則會在查詢數(shù)據(jù)庫以后再釋放鎖,這樣可以阻止并發(fā)情況下多個線程同時查詢數(shù)據(jù),詳情可參考BlockingCache的源碼。?
    簡單理解,也就是設(shè)置true時,在進(jìn)行增刪改之后的并發(fā)查詢,只會有一條去數(shù)據(jù)庫查詢,而不會并發(fā)

  • eviction:eviction,驅(qū)逐的意思。也就是元素驅(qū)逐算法,默認(rèn)是LRU,對應(yīng)的就是LruCache,其默認(rèn)只保存1024個Key,超出時按照最近最少使用算法進(jìn)行驅(qū)逐,詳情請參考LruCache的源碼。如果想使用自己的算法,則可以將該值指定為自己的驅(qū)逐算法實(shí)現(xiàn)類,只需要自己的類實(shí)現(xiàn)Mybatis的Cache接口即可。除了LRU以外,系統(tǒng)還提供了FIFO(先進(jìn)先出,對應(yīng)FifoCache)、SOFT(采用軟引用存儲Value,便于垃圾回收,對應(yīng)SoftCache)和WEAK(采用弱引用存儲Value,便于垃圾回收,對應(yīng)WeakCache)這三種策略。?
    這里,根據(jù)個人需求選擇了,沒什么要求的話,默認(rèn)的LRU即可

  • flushInterval:清空緩存的時間間隔,單位是毫秒,默認(rèn)是不會清空的。當(dāng)指定了該值時會再用ScheduleCache包裝一次,其會在每次對緩存進(jìn)行操作時判斷距離最近一次清空緩存的時間是否超過了flushInterval指定的時間,如果超出了,則清空當(dāng)前的緩存,詳情可參考ScheduleCache的實(shí)現(xiàn)。

  • readOnly:是否只讀?
    默認(rèn)為false。當(dāng)指定為false時,底層會用SerializedCache包裝一次,其會在寫緩存的時候?qū)⒕彺鎸ο筮M(jìn)行序列化,然后在讀緩存的時候進(jìn)行反序列化,這樣每次讀到的都將是一個新的對象,即使你更改了讀取到的結(jié)果,也不會影響原來緩存的對象,即非只讀,你每次拿到這個緩存結(jié)果都可以進(jìn)行修改,而不會影響原來的緩存結(jié)果;?
    當(dāng)指定為true時那就是每次獲取的都是同一個引用,對其修改會影響后續(xù)的緩存數(shù)據(jù)獲取,這種情況下是不建議對獲取到的緩存結(jié)果進(jìn)行更改,意為只讀(不建議設(shè)置為true)。?
    這是Mybatis二級緩存讀寫和只讀的定義,可能與我們通常情況下的只讀和讀寫意義有點(diǎn)不同。每次都進(jìn)行序列化和反序列化無疑會影響性能,但是這樣的緩存結(jié)果更安全,不會被隨意更改,具體可根據(jù)實(shí)際情況進(jìn)行選擇。詳情可參考SerializedCache的源碼。

  • size:用來指定緩存中最多保存的Key的數(shù)量。其是針對LruCache而言的,LruCache默認(rèn)只存儲最多1024個Key,可通過該屬性來改變默認(rèn)值,當(dāng)然,如果你通過eviction指定了自己的驅(qū)逐算法,同時自己的實(shí)現(xiàn)里面也有setSize方法,那么也可以通過cache的size屬性給自定義的驅(qū)逐算法里面的size賦值。

  • type:type屬性用來指定當(dāng)前底層緩存實(shí)現(xiàn)類,默認(rèn)是PerpetualCache,如果我們想使用自定義的Cache,則可以通過該屬性來指定,對應(yīng)的值是我們自定義的Cache的全路徑名稱。

  • cache-ref元素定義

    cache-ref元素可以用來指定其它Mapper.xml中定義的Cache,有的時候可能我們多個不同的Mapper需要共享同一個緩存的?
    是希望在MapperA中緩存的內(nèi)容在MapperB中可以直接命中的,這個時候我們就可以考慮使用cache-ref,這種場景只需要保證它們的緩存的Key是一致的即可命中,二級緩存的Key是通過Executor接口的createCacheKey()方法生成的,其實(shí)現(xiàn)基本都是BaseExecutor,源碼如下。

    ? ?public CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) { ? ? ? ?if (this.closed) { ? ? ? ? ? ?throw new ExecutorException("Executor was closed.");} else {CacheKey cacheKey = new CacheKey();cacheKey.update(ms.getId());cacheKey.update(rowBounds.getOffset());cacheKey.update(rowBounds.getLimit());cacheKey.update(boundSql.getSql());List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();TypeHandlerRegistry typeHandlerRegistry = ms.getConfiguration().getTypeHandlerRegistry(); ? ? ? ? ? ?for(int i = 0; i < parameterMappings.size(); ++i) {ParameterMapping parameterMapping = (ParameterMapping)parameterMappings.get(i); ? ? ? ? ? ? ? ?if (parameterMapping.getMode() != ParameterMode.OUT) {String propertyName = parameterMapping.getProperty();Object value; ? ? ? ? ? ? ? ? ? ?if (boundSql.hasAdditionalParameter(propertyName)) {value = boundSql.getAdditionalParameter(propertyName);} else if (parameterObject == null) {value = null;} else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {value = parameterObject;} else {MetaObject metaObject = this.configuration.newMetaObject(parameterObject);value = metaObject.getValue(propertyName);}cacheKey.update(value);}} ? ? ? ? ? ?return cacheKey;}}

    打個比方我想在MenuMapper.xml中的查詢都使用在UserMapper.xml中定義的Cache,則可以通過cache-ref元素的namespace屬性指定需要引用的Cache所在的namespace,即UserMapper.xml中的定義的namespace,假設(shè)在UserMapper.xml中定義的namespace是cn.chenhaoxiang.dao.UserMapper,則在MenuMapper.xml的cache-ref應(yīng)該定義如下。

    <cache-ref namespace="cn.chenhaoxiang.dao.UserMapper"/>
    • 1

    這樣這兩個Mapper就共享同一個緩存了

    自定義cache就不介紹了。

    測試二級緩存

    ?

    查詢測試

    /*** Created with IntelliJ IDEA.* User: 陳浩翔.* Date: 2018/1/10.* Time: 下午 10:15.* Explain:*/@RunWith(SpringJUnit4ClassRunner.class)//配置了@ContextConfiguration注解并使用該注解的locations屬性指明spring和配置文件之后@ContextConfiguration(locations = {"classpath:spring.xml","classpath:spring-mybatis.xml"})public class MyBatisTestBySpringTestFramework {//注入userService@Autowiredprivate UserService userService; ? ?@Testpublic void testGetUserId(){String userId = "4e07f3963337488e81716cfdd8a0fe04";User user = userService.getUserById(userId);System.out.println(user); ? ? ? ?//前面說到spring和MyBatis整合User user2 = userService.getUserById(userId);System.out.println("user2:"+user2);} }

    ?

    接下來我們把Mapper中的cache元素刪除,不使用二級緩存

    ?

    再運(yùn)行測試

    ?

    對二級緩存進(jìn)行了以下測試,獲取兩個不同的SqlSession(前面有說,Spring和MyBatis集成,每次都是不同的SqlSession)執(zhí)行兩條相同的SQL,在未指定Cache時Mybatis將查詢兩次數(shù)據(jù)庫,在指定了Cache時Mybatis只查詢了一次數(shù)據(jù)庫,第二次是從緩存中拿的。

    Cache Hit Ratio 表示緩存命中率。?
    開啟二級緩存后,每執(zhí)行一次查詢,系統(tǒng)都會計(jì)算一次二級緩存的命中率。?
    第一次查詢也是先從緩存中查詢,只不過緩存中一定是沒有的。?
    所以會再從DB中查詢。由于二級緩存中不存在該數(shù)據(jù),所以命中率為0.但第二次查詢是從二級緩存中讀取的,所以這一次的命中率為1/2=0.5。?
    當(dāng)然,若有第三次查詢,則命中率為1/3=0.66?
    0.5這個值可以從上面開啟cache的圖看出來,0.0的值未截取到~漏掉了~

    注意:?
    增刪改操作,無論是否進(jìn)行提交sqlSession.commit(),均會清空一級、二級緩存,使查詢再次從DB中select。?
    說明:?
    二級緩存的清空,實(shí)質(zhì)上是對所查找key對應(yīng)的value置為null,而非將

    二級緩存的使用原則

  • 只能在一個命名空間下使用二級緩存?
    由于二級緩存中的數(shù)據(jù)是基于namespace的,即不同namespace中的數(shù)據(jù)互不干擾。在多個namespace中若均存在對同一個表的操作,那么這多個namespace中的數(shù)據(jù)可能就會出現(xiàn)不一致現(xiàn)象。

  • 在單表上使用二級緩存?
    如果一個表與其它表有關(guān)聯(lián)關(guān)系,那么久非常有可能存在多個namespace對同一數(shù)據(jù)的操作。而不同namespace中的數(shù)據(jù)互補(bǔ)干擾,所以就有可能出現(xiàn)多個namespace中的數(shù)據(jù)不一致現(xiàn)象。

  • 查詢多于修改時使用二級緩存?
    在查詢操作遠(yuǎn)遠(yuǎn)多于增刪改操作的情況下可以使用二級緩存。因?yàn)槿魏卧鰟h改操作都將刷新二級緩存,對二級緩存的頻繁刷新將降低系統(tǒng)性能。

  • 總結(jié)

    以上是生活随笔為你收集整理的MyBatis】MyBatis一级缓存和二级缓存的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。