MyBatis二級(jí)緩存

一、MyBatis緩存官網(wǎng)內(nèi)容

MyBatis-官網(wǎng)-緩存

1.1 緩存

MyBatis 內(nèi)置了一個(gè)強(qiáng)大的事務(wù)性查詢緩存機(jī)制,它可以非常方便地配置和定制。 為了使它更加強(qiáng)大而且易于配置,我們對(duì) MyBatis 3 中的緩存實(shí)現(xiàn)進(jìn)行了許多改進(jìn)。默認(rèn)情況下,只啟用了本地的會(huì)話緩存,它僅僅對(duì)一個(gè)會(huì)話(SqlSession)中的數(shù)據(jù)進(jìn)行緩存。 要啟用全局的二級(jí)緩存,只需要在你的 SQL 映射文件中添加一行:

<cache/>

基本上就是這樣,這個(gè)簡(jiǎn)單語句的效果如下:

  • 映射語句文件中的所有 select 語句的結(jié)果將會(huì)被緩存。
  • 映射語句文件中的所有 insert、update 和 delete 語句會(huì)刷新緩存。
  • 緩存會(huì)使用最近最少使用算法(LRU, Least Recently Used)算法來清除不需要的緩存。
  • 緩存不會(huì)定時(shí)進(jìn)行刷新(也就是說,沒有刷新間隔)。
  • 緩存會(huì)保存列表或?qū)ο螅o論查詢方法返回哪種)的 1024 個(gè)引用。
  • 緩存會(huì)被視為讀/寫緩存,這意味著獲取到的對(duì)象并不是共享的,可以安全地被調(diào)用者修改,而不干擾其他調(diào)用者或線程所做的潛在修改。

緩存只作用于 cache 標(biāo)簽所在的映射文件中的語句。如果你混合使用 Java API 和 XML 映射文件,在共用接口中的語句將不會(huì)被默認(rèn)緩存。你需要使用@CacheNamespaceRef 注解指定緩存作用域。這些屬性可以通過 cache 元素的屬性來修改。比如:

<cache
 eviction="FIFO"
 flushInterval="60000"
 size="512"
 readOnly="true"/>

這個(gè)更高級(jí)的配置創(chuàng)建了一個(gè) FIFO 緩存,每隔 60 秒刷新,最多可以存儲(chǔ)結(jié)果對(duì)象或列表的 512 個(gè)引用,而且返回的對(duì)象被認(rèn)為是只讀的,因此對(duì)它們進(jìn)行修改可能會(huì)在不同線程中的調(diào)用者產(chǎn)生沖突??捎玫那宄呗杂校?/p>

  • LRU – 最近最少使用:移除最長(zhǎng)時(shí)間不被使用的對(duì)象。
  • FIFO – 先進(jìn)先出:按對(duì)象進(jìn)入緩存的順序來移除它們。
  • SOFT – 軟引用:基于垃圾回收器狀態(tài)和軟引用規(guī)則移除對(duì)象。
  • WEAK – 弱引用:更積極地基于垃圾收集器狀態(tài)和弱引用規(guī)則移除對(duì)象。
    默認(rèn)的清除策略是 LRU。

flushInterval(刷新間隔)屬性可以被設(shè)置為任意的正整數(shù),設(shè)置的值應(yīng)該是一個(gè)以毫秒為單位的合理時(shí)間量。 默認(rèn)情況是不設(shè)置,也就是沒有刷新間隔,緩存僅僅會(huì)在調(diào)用語句時(shí)刷新。

size(引用數(shù)目)屬性可以被設(shè)置為任意正整數(shù),要注意欲緩存對(duì)象的大小和運(yùn)行環(huán)境中可用的內(nèi)存資源。默認(rèn)值是 1024。

readOnly(只讀)屬性可以被設(shè)置為 true 或 false。只讀的緩存會(huì)給所有調(diào)用者返回緩存對(duì)象的相同實(shí)例。 因此這些對(duì)象不能被修改。這就提供了可觀的性能提升。而可讀寫的緩存會(huì)(通過序列化)返回緩存對(duì)象的拷貝。 速度上會(huì)慢一些,但是更安全,因此默認(rèn)值是 false。

二級(jí)緩存是事務(wù)性的。這意味著,當(dāng) SqlSession 完成并提交時(shí),或是完成并回滾,但沒有執(zhí)行 flushCache=true 的 insert/delete/update 語句時(shí),緩存會(huì)獲得更新。

1.2 使用自定義緩存

除了上述自定義緩存的方式,你也可以通過實(shí)現(xiàn)你自己的緩存,或?yàn)槠渌谌骄彺娣桨竸?chuàng)建適配器,來完全覆蓋緩存行為。

<cache type="com.domain.something.MyCustomCache"/>

這個(gè)示例展示了如何使用一個(gè)自定義的緩存實(shí)現(xiàn)。type 屬性指定的類必須實(shí)現(xiàn) org.apache.ibatis.cache.Cache 接口,且提供一個(gè)接受 String 參數(shù)作為 id 的構(gòu)造器。 這個(gè)接口是 MyBatis 框架中許多復(fù)雜的接口之一,但是行為卻非常簡(jiǎn)單。

public interface Cache {
  String getId();
  int getSize();
  void putObject(Object key, Object value);
  Object getObject(Object key);
  boolean hasKey(Object key);
  Object removeObject(Object key);
  void clear();
}

為了對(duì)你的緩存進(jìn)行配置,只需要簡(jiǎn)單地在你的緩存實(shí)現(xiàn)中添加公有的 JavaBean 屬性,然后通過 cache 元素傳遞屬性值,例如,下面的例子將在你的緩存實(shí)現(xiàn)上調(diào)用一個(gè)名為 setCacheFile(String file) 的方法:

<cache type="com.domain.something.MyCustomCache">
  <property name="cacheFile" value="/tmp/my-custom-cache.tmp"/>
</cache>

你可以使用所有簡(jiǎn)單類型作為 JavaBean 屬性的類型,MyBatis 會(huì)進(jìn)行轉(zhuǎn)換。 你也可以使用占位符(如 ${cache.file}),以便替換成在配置文件屬性中定義的值。從版本 3.4.2 開始,MyBatis 已經(jīng)支持在所有屬性設(shè)置完畢之后,調(diào)用一個(gè)初始化方法。 如果想要使用這個(gè)特性,請(qǐng)?jiān)谀愕淖远x緩存類里實(shí)現(xiàn)org.apache.ibatis.builder.InitializingObject 接口。

public interface InitializingObject {
  void initialize() throws Exception;
}

上一節(jié)中對(duì)緩存的配置(如清除策略、可讀或可讀寫等),不能應(yīng)用于自定義緩存。

請(qǐng)注意,緩存的配置和緩存實(shí)例會(huì)被綁定到 SQL 映射文件的命名空間中。 因此,同一命名空間中的所有語句和緩存將通過命名空間綁定在一起。 每條語句可以自定義與緩存交互的方式,或?qū)⑺鼈兺耆懦诰彺嬷?,這可以通過在每條語句上使用兩個(gè)簡(jiǎn)單屬性來達(dá)成。 默認(rèn)情況下,語句會(huì)這樣來配置:

<select ... flushCache="false" useCache="true"/>
<insert ... flushCache="true"/>
<update ... flushCache="true"/>
<delete ... flushCache="true"/>

鑒于這是默認(rèn)行為,顯然你永遠(yuǎn)不應(yīng)該以這樣的方式顯式配置一條語句。但如果你想改變默認(rèn)的行為,只需要設(shè)置 flushCache 和 useCache 屬性。比如,某些情況下你可能希望特定 select 語句的結(jié)果排除于緩存之外,或希望一條 select 語句清空緩存。類似地,你可能希望某些 update 語句執(zhí)行時(shí)不要刷新緩存。

1.3 cache-ref

回想一下上一節(jié)的內(nèi)容,對(duì)某一命名空間的語句,只會(huì)使用該命名空間的緩存進(jìn)行緩存或刷新。 但你可能會(huì)想要在多個(gè)命名空間中共享相同的緩存配置和實(shí)例。要實(shí)現(xiàn)這種需求,你可以使用 cache-ref 元素來引用另一個(gè)緩存。

<cache-ref namespace="com.someone.application.data.SomeMapper"/>

二、補(bǔ)充

2.1 Mybatis緩存介紹

Mybatis提供查詢緩存,如果緩存中有數(shù)據(jù)就不用從數(shù)據(jù)庫中獲取,用于減輕數(shù)據(jù)壓力,提高系統(tǒng)性能。Mybatis的查詢緩存總共有兩級(jí),我們稱之為一級(jí)緩存和二級(jí)緩存。

一級(jí)緩存是SqlSession級(jí)別的緩存。在操作數(shù)據(jù)庫時(shí)需要構(gòu)造 sqlSession對(duì)象,在對(duì)象中有一個(gè)數(shù)據(jù)結(jié)構(gòu)(HashMap)用于存儲(chǔ)緩存數(shù)據(jù)。不同的sqlSession之間的緩存數(shù)據(jù)區(qū)域(HashMap)是互相不影響的。

二級(jí)緩存是Mapper(namespace)級(jí)別的緩存。多個(gè)SqlSession去操作同一個(gè)Mapper的sql語句,多個(gè)SqlSession可以共用二級(jí)緩存,二級(jí)緩存是跨SqlSession的。

2.2 一級(jí)緩存

Mybatis的一級(jí)緩存是默認(rèn)開啟的,其實(shí)指的是Mybaits中SqlSession對(duì)象的緩存。當(dāng)我們執(zhí)行查詢之后,查詢的結(jié)果會(huì)同時(shí)存入到 SqlSession為我們提供的一塊區(qū)域中。該區(qū)域的結(jié)構(gòu)是一個(gè)Map。當(dāng)我們?cè)俅尾樵兺瑯拥臄?shù)據(jù),Mybatis會(huì)先去SqlSession中查詢是否存在,存在的話直接返回,而不會(huì)再去數(shù)據(jù)庫查詢。這里要注意一個(gè)點(diǎn),因?yàn)橐患?jí)緩存是依賴于SqlSession對(duì)象的,當(dāng)SqlSession對(duì)象消失時(shí),Mybaits的一級(jí)緩存也就消失了。

當(dāng)Mybaits與Spring整合的時(shí)候,不在Spring事務(wù)的方法內(nèi),每次請(qǐng)求數(shù)據(jù)庫,都會(huì)新建一個(gè)SqlSession,這時(shí)候是使用不到一級(jí)緩存的,代碼示例如下:

public void test1(){
    MailConf mail1 = mailConfService.findByMailCode("11");
    System.out.println(mail1);
    MailConf mail2 = mailConfService.findByMailCode("11");
    System.out.println(mail2);
    System.out.println(mail1 == mail2);
}

可以看到,每次請(qǐng)求都新建了一個(gè)Sqlsession,返回的對(duì)象也不是同一個(gè)。

在帶Spring事務(wù)的方法內(nèi),每次請(qǐng)求數(shù)據(jù)庫,只新建一個(gè)SqlSession,這時(shí)候是可以使用到一級(jí)緩存的,代碼示例如下:

@Transactional(rollbackFor = Exception.class)
public void test1(){
    MailConf mail1 = mailConfService.findByMailCode("11");
    System.out.println(mail1);
    MailConf mail2 = mailConfService.findByMailCode("11");
    System.out.println(mail2);
    System.out.println(mail1 == mail2);
}

這樣就用到了一級(jí)緩存。

除了事務(wù)問題,還有一些問題也會(huì)影響到一級(jí)緩存,比如調(diào)用了Sqlsession的修改、添加、刪除、commit()、close()等方法時(shí),一級(jí)緩存也會(huì)被清空,底下示例就先刪除一條記錄,然后看下結(jié)果:

@Transactional(rollbackFor = Exception.class)
public void test1() {
    MailConf mail1 = mailConfService.findByMailCode("11");
    System.out.println(mail1);
    // 刪除
    mailConfService.deleteConf(2);
    MailConf mail2 = mailConfService.findByMailCode("11");
    System.out.println(mail2);
    System.out.println(mail1 == mail2);
}

同樣的代碼,不同的執(zhí)行結(jié)果,這時(shí)候也是沒有用到一級(jí)緩存。只要不是放在兩次查詢的中間,都會(huì)使用到一級(jí)緩存。

2.2 二級(jí)緩存
2.2.1 開啟二級(jí)緩存

Mybatis的二級(jí)緩存是默認(rèn)關(guān)閉的,它指的是MybatisSqlSessionFactory對(duì)象的緩存,由同一個(gè)SqlSessionFactory對(duì)象創(chuàng)建的SqlSession共享其二級(jí)緩存。這里注意:一個(gè)SqlSessionFactory對(duì)象可能創(chuàng)建多個(gè)SqlSession。

SpringBoot項(xiàng)目中二級(jí)緩存使用步驟:

  • 讓當(dāng)前的映射文件支持二級(jí)緩存(在xxx.xml中配置)
  • 讓當(dāng)前的操作支持二級(jí)緩存(在select標(biāo)簽中配置)
<settings>
  <setting "cacheEnabled"value="true"/>
</settings>

mapper.xml中加入標(biāo)簽<cache/>核心參數(shù)可不設(shè)置,使用默認(rèn)參數(shù)。開啟本mapper下的namespace的二級(jí)緩存,默認(rèn)使用的是mybatis提供的PerpetualCache。

第一次調(diào)用mapper下的SQL去查詢用戶信息。查詢到的信息會(huì)存到該mapper對(duì)應(yīng)的二級(jí)緩存區(qū)域內(nèi)。

第二次調(diào)用相同namespace下的mapper映射文件中相同的SQL去查詢用戶信息。會(huì)去對(duì)應(yīng)的二級(jí)緩存內(nèi)取結(jié)果。如果調(diào)用相同namespace下的mapper映射文件中的增刪改SQL,并執(zhí)行了commit操作。此時(shí)會(huì)清空該namespace下的二級(jí)緩存。

2.2.2 禁用二級(jí)緩存

默認(rèn)二級(jí)緩存的粒度是mapper級(jí)別的,但是如果在同一個(gè)mapper文件中某個(gè)查詢不想使用二級(jí)緩存的話,就需要對(duì)緩存的控制粒度更細(xì)。在select標(biāo)簽中設(shè)置useCache=false,可以禁用當(dāng)前select語句的二級(jí)緩存,即每次查詢都是去數(shù)據(jù)庫中查詢,默認(rèn)情況下是true,即該statement使用二級(jí)緩存。

<select id="findUserById" 
parameterType="int" resultType="com.kkb.mybatis.po.User"useCache="true">
    SELECT * FROM user WHERE id = #{id}
</select>

2.2.3 刷新二級(jí)緩存

通過flushCache屬性,可以控制select、insert、update、delete標(biāo)簽是否屬性二級(jí)緩存。

默認(rèn)情況下如果是select語句,那么flushCachefalse。如果是insert、update、delete語句,那么flushCachetrue。

如果查詢語句設(shè)置成true,那么每次查詢都是去數(shù)據(jù)庫查詢,即意味著該查詢的二級(jí)緩存失效。

如果增刪改語句設(shè)置成false,即使用二級(jí)緩存,那么如果在數(shù)據(jù)庫中修改了數(shù)據(jù),而緩存數(shù)據(jù)還是原來的,這個(gè)時(shí)候就會(huì)出現(xiàn)臟讀。

flushCache設(shè)置如下:

<selectid="findUserById"parameterType="int" resultType="com.kkb.mybatis.po.User" useCache="true" flushCache="true">
   SELECT * FROM user WHERE id =#{id}
</select>
2.3 為什么使用緩存

使用緩存可以減少和數(shù)據(jù)庫的交互次數(shù),提高執(zhí)行效率,并且可以保護(hù)數(shù)據(jù)庫不被高并發(fā)的請(qǐng)求拖死。

2.3 適用于緩存的數(shù)據(jù)
  • 經(jīng)常查詢并且不經(jīng)常被改變的數(shù)據(jù)。
  • 數(shù)據(jù)的正確與否對(duì)最終結(jié)果影響不大的數(shù)據(jù)。
2.4 不適用于緩存的數(shù)據(jù)
  • 經(jīng)常改變的數(shù)據(jù)。
  • 數(shù)據(jù)的正確與否對(duì)最終結(jié)果影響很大的。
2.5 總結(jié)

1、Mybatis的一級(jí)緩存默認(rèn)開啟,而二級(jí)緩存默認(rèn)關(guān)閉。

2、Mybatis的一級(jí)緩存指的是Mybaits中SqlSession對(duì)象的緩存,而二級(jí)緩存指的是SqlSessionFactory對(duì)象的緩存。一個(gè)SqlSessionFactory對(duì)象包括多個(gè)SqlSession對(duì)象。

3、SqlSession對(duì)象中存放的是返回?cái)?shù)據(jù)的對(duì)象,而SqlSessionFactory對(duì)象中存放的是數(shù)據(jù),不是對(duì)象。

4、Mybatis和Spring整合的時(shí)候,一級(jí)緩存與事務(wù)有關(guān),而二級(jí)緩存與事務(wù)無關(guān)。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容