MyBatis筆記 | 詳解MyBatis緩存機(jī)制

概述

Mybatis包含一個(gè)非常強(qiáng)大的查詢緩存特性奈应,它可以非常方便地配置和定制。緩存可以極大的提升查詢效率。MyBatis系統(tǒng)中默認(rèn)定義了兩級(jí)緩存:一級(jí)緩存和二級(jí)緩存跃脊。

  • 默認(rèn)情況下,只有一級(jí)緩存(SqlSession級(jí)別的緩存苛吱,也稱為本地緩存)開啟酪术。
  • 二級(jí)緩存需要手動(dòng)開啟和配置,它是基于namespace級(jí)別的緩存翠储,即全局范圍緩存拼缝。
  • 為了提高擴(kuò)展性,MyBatis定義了緩存接口Cache彰亥。我們可以通過(guò)實(shí)現(xiàn)Cache接口來(lái)自定義二級(jí)緩存咧七。

一級(jí)緩存(本地緩存)

是SqlSession級(jí)別的緩存,一級(jí)緩存是一致開啟的任斋,是一個(gè)SqlSession的Map
與數(shù)據(jù)庫(kù)同一次會(huì)話期間查詢到的數(shù)據(jù)會(huì)放在本地緩存中继阻,以后如果需要獲取相同的數(shù)據(jù),直接從緩存中拿废酷,沒(méi)必要再去查詢數(shù)據(jù)庫(kù)瘟檩。

    @Test
    public void testFirstLevelCache() throws IOException {
        SqlSessionFactory factory = getSqlSessionFactory();
        SqlSession session = factory.openSession();
        try{
            EmployeeMapper mapper = session.getMapper(EmployeeMapper.class);
            Employee emp1 = mapper.getEmpById(1);
            System.out.println(emp1);
            Employee emp2 = mapper.getEmpById(1);
            System.out.println(emp2);
        }finally {
            session.close();
        }
    }

在上述代碼中我們定義了一個(gè)emp1和emp2對(duì)象,都是查詢的tb1_employee表中的id為1的記錄澈蟆,正常的思維來(lái)說(shuō)墨辛,我們覺(jué)得這樣調(diào)用會(huì)發(fā)送兩條sql語(yǔ)句,而實(shí)際上MyBatis只發(fā)送了一條sql語(yǔ)句:

圖1:控制臺(tái)打印的sql語(yǔ)句

一級(jí)緩存失效的四種情況

1.當(dāng)sqlSession不同時(shí)

因?yàn)橐患?jí)緩存是SqlSession級(jí)別的緩存,那么當(dāng)SqlSession不同時(shí)摩骨,那么前一個(gè)sqlSession的一級(jí)緩存對(duì)于第二個(gè)SqlSession來(lái)說(shuō)肯定是失效的,我們來(lái)看個(gè)例子:

    @Test
    public void testFirstLevelCache() throws IOException {
        SqlSessionFactory factory = getSqlSessionFactory();
        SqlSession session = factory.openSession();
        SqlSession session1 = factory.openSession();
        try{
            EmployeeMapper mapper = session.getMapper(EmployeeMapper.class);
            EmployeeMapper mapper1 = session1.getMapper(EmployeeMapper.class);
            Employee emp = mapper.getEmpById(1);
            System.out.println(emp);
            Employee emp2 = mapper1.getEmpById(1);
            System.out.println(emp2);
        }finally {
            session.close();
        }
    }

對(duì)于這個(gè)例子幕屹,我們定義了兩個(gè)SqlSession太惠,并分別使用它來(lái)創(chuàng)建兩個(gè)EmployeeMapper的代理對(duì)象磨淌,然后empemp2是兩個(gè)代理對(duì)象分別調(diào)用方法查詢id=1的字段,我們看圖2可知凿渊,其發(fā)了兩次sql語(yǔ)句梁只。

圖2:控制臺(tái)打印的sql語(yǔ)句

2.當(dāng)SqlSession相同,但是查詢條件不同時(shí)

    @Test
    public void testFirstLevelCache() throws IOException {
        SqlSessionFactory factory = getSqlSessionFactory();
        SqlSession session = factory.openSession();
        try{
            EmployeeMapper mapper = session.getMapper(EmployeeMapper.class);
            Employee emp1 = mapper.getEmpById(1);
            Employee emp2 = mapper.getEmpById(2);
            System.out.println(emp1);
            System.out.println(emp2);
        }finally {
            session.close();
        }
    }

上述例子中埃脏,一個(gè)是查詢id=1搪锣,另一個(gè)是查詢id=2的記錄。因?yàn)榈诙€(gè)查詢與第一個(gè)查詢的條件不一樣彩掐,所以第二個(gè)查詢?cè)诒镜鼐彺嬷袥](méi)有對(duì)應(yīng)的數(shù)據(jù)构舟,因此理所應(yīng)當(dāng)會(huì)發(fā)送sql語(yǔ)句,結(jié)果如下圖3所示:

圖3

3.當(dāng)SqlSession相同佩谷,但是多次查詢之間進(jìn)行了增刪改時(shí)

    @Test
    public void testFirstLevelCache() throws IOException {
        SqlSessionFactory factory = getSqlSessionFactory();
        SqlSession session = factory.openSession();
        try{
            EmployeeMapper mapper = session.getMapper(EmployeeMapper.class);
            Employee emp1 = mapper.getEmpById(1);
            mapper.addEmp(new Employee(null,"1","1","1",new Department()));
            System.out.println("添加成功");
            Employee emp2 = mapper.getEmpById(1);
            System.out.println(emp1);
            System.out.println(emp2);
        }finally {
            session.close();
        }
    }

上述例子中先查詢了一次id=1旁壮,然后插入了一條記錄,然后再查詢一次id=1谐檀。這樣的話抡谐,兩次查詢都會(huì)發(fā)送sql語(yǔ)句。因?yàn)榭赡苓@次增刪改操作會(huì)對(duì)當(dāng)前數(shù)據(jù)有影響桐猬,結(jié)果如下圖4所示麦撵,紅色方框?yàn)榍昂髢纱尾樵兊膕ql語(yǔ)句,綠色為添加操作的語(yǔ)句:

圖4

4.SqlSession相同溃肪,手動(dòng)清除了一級(jí)緩存(緩存清空)

    @Test
    public void testFirstLevelCache() throws IOException {
        SqlSessionFactory factory = getSqlSessionFactory();
        SqlSession session = factory.openSession();
        try{
            EmployeeMapper mapper = session.getMapper(EmployeeMapper.class);
            Employee emp1 = mapper.getEmpById(1);
            session.clearCache();   //清除緩存
            Employee emp2 = mapper.getEmpById(1);
            System.out.println(emp1);
            System.out.println(emp2);
        }finally {
            session.close();
        }
    }

上述代碼中免胃,查詢了一次id=1記錄之后,使用了clearCache()清除了sqlSession的緩存惫撰,然后再查詢了id=1記錄羔沙。此時(shí)會(huì)發(fā)現(xiàn)其也是發(fā)送了兩次sql:

圖5


二級(jí)緩存

一個(gè)namespace對(duì)應(yīng)一個(gè)二級(jí)緩存,即是在namespace中全局范圍的厨钻。

工作機(jī)制

  • 一個(gè)會(huì)話扼雏,查詢一條數(shù)據(jù),這個(gè)數(shù)據(jù)就會(huì)被放在當(dāng)前會(huì)話的一級(jí)緩存中夯膀;
  • 如果會(huì)話關(guān)閉诗充,一級(jí)緩存中的數(shù)據(jù)會(huì)被保存到二級(jí)緩存中;當(dāng)有新的會(huì)話查詢信息時(shí)诱建,就可以參照二級(jí)緩存中的數(shù)據(jù)蝴蜓。
  • 不同namespace查出的數(shù)據(jù)會(huì)放在自己對(duì)應(yīng)的緩存中(map中)
  • 需要注意的是,查出的數(shù)據(jù)會(huì)被默認(rèn)存放到一級(jí)緩存中;只有會(huì)話提交或者關(guān)閉之后茎匠,一級(jí)緩存中的數(shù)據(jù)才會(huì)轉(zhuǎn)移到二級(jí)緩存中格仲。

使用步驟

1.開啟全局二級(jí)緩存配置
    <settings>
        <setting name="cacheEnabled" value="true"/>
    </settings>
2.在SQL映射文件中配置使用二級(jí)緩存:在<mapper>標(biāo)簽中配置<cache>子標(biāo)簽即可

有如下的屬性:

  • eviction:緩存回收策略
  • flushInterval:緩存刷新間隔,指定緩存多長(zhǎng)時(shí)間(以毫秒為單位)清空一次汽抚,默認(rèn)不清空
  • readOnly:緩存是否只讀抓狭。
    如果為true伯病,則會(huì)只讀造烁。mybatis認(rèn)為所有從緩存中獲取數(shù)據(jù)的操作都是只讀操作,不會(huì)修改數(shù)據(jù)午笛。mybatis為了加快獲取速度惭蟋,直接就會(huì)將數(shù)據(jù)在緩存中的引用交給用戶,不安全药磺,速度快告组。
    如果為false,則為非只讀癌佩。mybatis認(rèn)為所有從緩存中獲取數(shù)據(jù)可能會(huì)被修改木缝,所以mybatis會(huì)利用序列化和反序列化的技術(shù)克隆一份新的數(shù)據(jù)給你,安全围辙,速度慢我碟。
  • size:緩存存放元素的大小。
  • type:指定自定義緩存的全類名姚建。實(shí)現(xiàn)Cache接口即可矫俺。
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.cerr.mybatis.dao.EmployeeMapperDynamicSQL">
    <!-- 配置使用二級(jí)緩存 -->
    <cache eviction="FIFO" flushInterval="60000" readOnly="false" size="1024"></cache>
</mapper>
3.我們的POJO需要實(shí)現(xiàn)序列化接口Serializable
public class Employee implements Serializable{

}
public class Department implements Serializable{

}

在測(cè)試方法中:

    @Test
    public void testSecondLevelCache() throws IOException{
        SqlSessionFactory factory = getSqlSessionFactory();
        SqlSession session = factory.openSession();
        SqlSession session1 = factory.openSession();
        try{
            EmployeeMapper mapper = session.getMapper(EmployeeMapper.class);
            EmployeeMapper mapper1 = session1.getMapper(EmployeeMapper.class);
            Employee employee1 = mapper.getEmpById(1);
            System.out.println(employee1);
            session.close();
            Employee employee2 = mapper1.getEmpById(1);
            System.out.println(employee2);
            session1.close();
        }finally {}
    }

使用了不同的SqlSession獲取的代理對(duì)象查詢id=1的字段(每個(gè)SqlSession查詢完就關(guān)閉)。則只會(huì)發(fā)送一條SQL語(yǔ)句掸冤。不過(guò)要注意的一點(diǎn)是厘托,得先把第一個(gè)SqlSession關(guān)閉后查使用第二個(gè)SqlSession查詢時(shí),二級(jí)緩存中才有數(shù)據(jù)稿湿;如果第一個(gè)沒(méi)關(guān)閉铅匹,則二級(jí)緩存中是沒(méi)有數(shù)據(jù)的

圖6

與緩存有關(guān)的設(shè)置及屬性

全局配置中的cacheEnabled屬性

如果設(shè)置為true饺藤,則開啟二級(jí)緩存包斑;如果設(shè)置為false,則會(huì)關(guān)閉二級(jí)緩存策精,但是一級(jí)緩存可用舰始。

SQL映射文件<select>中的useCache屬性

如果設(shè)置為true,二級(jí)緩存可用咽袜;如果設(shè)置為false丸卷,也會(huì)關(guān)閉二級(jí)緩存,一級(jí)緩存依然使用询刹。

SQL映射文件<insert>谜嫉、<update>萎坷、<delete>中的flushCache屬性

默認(rèn)為true,即每次增刪改執(zhí)行完成后都會(huì)清除緩存沐兰,一級(jí)緩存和二級(jí)緩存都會(huì)被清空哆档。
<select>標(biāo)簽中也有這個(gè)屬性,默認(rèn)為false住闯,在此我們不討論瓜浸。

SqlSession中的clearCache()方法

只會(huì)清除當(dāng)前SqlSession的一級(jí)緩存,不會(huì)清空二級(jí)緩存比原。

全局配置中的localCacheScope屬性

稱為本地緩存作用域插佛,當(dāng)取值為SESSION時(shí),當(dāng)前會(huì)話的所有數(shù)據(jù)保存在會(huì)話緩存中量窘;當(dāng)取值為STATEMENT時(shí)雇寇,就禁用了一級(jí)緩存。


緩存原理

圖7:緩存原理示意圖
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末蚌铜,一起剝皮案震驚了整個(gè)濱河市锨侯,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌冬殃,老刑警劉巖囚痴,帶你破解...
    沈念sama閱讀 219,110評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異造壮,居然都是意外死亡渡讼,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,443評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門耳璧,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)成箫,“玉大人,你說(shuō)我怎么就攤上這事旨枯〉挪” “怎么了?”我有些...
    開封第一講書人閱讀 165,474評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵攀隔,是天一觀的道長(zhǎng)皂贩。 經(jīng)常有香客問(wèn)我,道長(zhǎng)昆汹,這世上最難降的妖魔是什么明刷? 我笑而不...
    開封第一講書人閱讀 58,881評(píng)論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮满粗,結(jié)果婚禮上辈末,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好挤聘,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,902評(píng)論 6 392
  • 文/花漫 我一把揭開白布轰枝。 她就那樣靜靜地躺著,像睡著了一般组去。 火紅的嫁衣襯著肌膚如雪鞍陨。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,698評(píng)論 1 305
  • 那天从隆,我揣著相機(jī)與錄音诚撵,去河邊找鬼。 笑死广料,一個(gè)胖子當(dāng)著我的面吹牛砾脑,可吹牛的內(nèi)容都是我干的幼驶。 我是一名探鬼主播艾杏,決...
    沈念sama閱讀 40,418評(píng)論 3 419
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼盅藻!你這毒婦竟也來(lái)了购桑?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,332評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤氏淑,失蹤者是張志新(化名)和其女友劉穎勃蜘,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體假残,經(jīng)...
    沈念sama閱讀 45,796評(píng)論 1 316
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡缭贡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,968評(píng)論 3 337
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了辉懒。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片阳惹。...
    茶點(diǎn)故事閱讀 40,110評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖眶俩,靈堂內(nèi)的尸體忽然破棺而出莹汤,到底是詐尸還是另有隱情,我是刑警寧澤颠印,帶...
    沈念sama閱讀 35,792評(píng)論 5 346
  • 正文 年R本政府宣布纲岭,位于F島的核電站,受9級(jí)特大地震影響线罕,放射性物質(zhì)發(fā)生泄漏止潮。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,455評(píng)論 3 331
  • 文/蒙蒙 一钞楼、第九天 我趴在偏房一處隱蔽的房頂上張望喇闸。 院中可真熱鬧,春花似錦、人聲如沸仅偎。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,003評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)橘沥。三九已至窗轩,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間座咆,已是汗流浹背痢艺。 一陣腳步聲響...
    開封第一講書人閱讀 33,130評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留介陶,地道東北人堤舒。 一個(gè)月前我還...
    沈念sama閱讀 48,348評(píng)論 3 373
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像哺呜,于是被迫代替她去往敵國(guó)和親舌缤。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,047評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容