1. 程式人生 > >【MyBatis學習8】MyBatis中的二級快取

【MyBatis學習8】MyBatis中的二級快取

1. 二級快取的原理

  前面介紹了,mybatis中的二級快取是mapper級別的快取,值得注意的是,不同的mapper都有一個二級快取,也就是說,不同的mapper之間的二級快取是互不影響的。為了更加清楚的描述二級快取,先來看一個示意圖: 
二級快取 
  從圖中可以看出:

  1. sqlSession1去查詢使用者id為1的使用者資訊,查詢到使用者資訊會將查詢資料儲存到該UserMapper的二級快取中。
  2. 如果SqlSession3去執行相同 mapper下sql,執行commit提交,則會清空該UserMapper下二級快取區域的資料。
  3. sqlSession2去查詢使用者id為1的使用者資訊,去快取中找是否存在資料,如果存在直接從快取中取出資料。

  快取的執行原理和前面提到的一級快取是差不多的,二級快取與一級快取區別在於二級快取的範圍更大,多個sqlSession可以共享一個mapper中的二級快取區域。mybatis是如何區分不同mapper的二級快取區域呢?它是按照不同mapper有不同的namespace來區分的,也就是說,如果兩個mapper的namespace相同,即使是兩個mapper,那麼這兩個mapper中執行sql查詢到的資料也將存在相同的二級快取區域中。

2. 二級快取的使用

  明白了mybatis中二級快取的原理後,接下來就是如何使用二級快取了。在使用之前,首先得開啟二級快取的開關。

2.1 開啟二級快取

  由於mybaits的二級快取是mapper範圍級別,所以除了在SqlMapConfig.xml設定二級快取的總開關外,還要在具體的mapper.xml中開啟二級快取。設定如下: 
開啟二級快取 
  這是在SqlMapConfig.xml中設定的,還得在具體的mapper.xml中設定,如下: 
mapper中的二級快取 
  可以看到,具體的mapper中僅僅就一個<cache>標籤,並沒有配置啥東西,這是因為mybatis中有預設的實現,我們如果不配置,那麼就預設使用那個預設的實現。在mybatis的核心包裡有cache的介面和這個預設的實現,我截個圖: 
二級快取預設實現類 
  所以就明白了,為啥不用配置都可以使用,mybatis中也就只有這一個預設實現類,如果不使用mybatis的預設二級快取的話,就需要自己實現cache介面,然後再在mapper.xml中配置一下了,關於這個我在下面再談,現在先把二級快取用起來!

2.2 將po類實現Serializable介面

  開啟了二級快取後,還需要將要快取的pojo實現Serializable介面,為了將快取資料取出執行反序列化操作,因為二級快取資料儲存介質多種多樣,不一定只存在記憶體中,有可能存在硬碟中,如果我們要再取這個快取的話,就需要反序列化了。所以建議mybatis中的pojo都去實現Serializable介面。下面以User為例截個圖: 
序列化

2.3 測試mybatis的二級快取

@Test
public void testCache2() throws Exception {
    SqlSession sqlSession1 = sqlSessionFactory.openSession();
    SqlSession sqlSession2 = sqlSessionFactory.openSession();
    SqlSession sqlSession3 = sqlSessionFactory.openSession();
    // 建立代理物件
    UserMapper userMapper1 = sqlSession1.getMapper(UserMapper.class);
    // 第一次發起請求,查詢id為1的使用者
    User user1 = userMapper1.findUserById(1);
    System.out.println(user1);  
    //這裡執行關閉操作,將sqlsession中的資料寫到二級快取區域
    sqlSession1.close();

    //sqlSession3用來清空快取的,如果要測試二級快取,需要把該部分註釋掉
    //使用sqlSession3執行commit()操作
    UserMapper userMapper3 = sqlSession3.getMapper(UserMapper.class);
    User user  = userMapper3.findUserById(1);
    user.setUsername("倪升武");
    userMapper3.updateUser(user);
    //執行提交,清空UserMapper下邊的二級快取
    sqlSession3.commit();
    sqlSession3.close();

    UserMapper userMapper2 = sqlSession2.getMapper(UserMapper.class);
    // 第二次發起請求,查詢id為1的使用者
    User user2 = userMapper2.findUserById(1);
    System.out.println(user2);

    sqlSession2.close();

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31

  我們先把sqlSession3部分註釋掉來測試一下二級快取的結果: 
二級快取
  當我們把sqlSession3部分加上後,再測試一下二級快取結果: 
二級快取2
  到這裡,就明白了mybatis中二級快取的執行原理了,這個跟hibernate還是有點像的。也可以對照著我寫的hibernate的二級快取的博文看(不過建議把下面的ehcache部分看完再對照hibernate看)。

2.4 其他配置(useCache和flushCache)

  mybatis中還可以配置userCache和flushCache等配置項,userCache是用來設定是否禁用二級快取的,在statement中設定useCache=false可以禁用當前select語句的二級快取,即每次查詢都會發出sql去查詢,預設情況是true,即該sql使用二級快取。

<select id="findOrderListResultMap" resultMap="ordersUserMap" useCache="false">
  • 1

  這種情況是針對每次查詢都需要最新的資料sql,要設定成useCache=false,禁用二級快取,直接從資料庫中獲取。 
在mapper的同一個namespace中,如果有其它insert、update、delete操作資料後需要重新整理快取,如果不執行重新整理快取會出現髒讀。 設定statement配置中的flushCache=”true” 屬性,預設情況下為true,即重新整理快取,如果改成false則不會重新整理。使用快取時如果手動修改資料庫表中的查詢資料會出現髒讀。

<insert id="insertUser" parameterType="cn.itcast.mybatis.po.User" flushCache="true">
  • 1

  一般下執行完commit操作都需要重新整理快取,flushCache=true表示重新整理快取,這樣可以避免資料庫髒讀。所以我們不用設定,預設即可,這裡只是提一下。

3. MyBatis整合ehcache分散式快取框架

3.1 問題的由來

  上面的部分主要總結了一下mybatis中二級快取的使用,但是mybatis中預設自帶的二級快取有個弊端,即無法實現分散式快取,什麼意思呢?就是說快取的資料在自己的伺服器上,假設現在有兩個伺服器A和B,使用者訪問的時候訪問了A伺服器,查詢後的快取就會放在A伺服器上,假設現在有個使用者訪問的是B伺服器,那麼他在B伺服器上就無法獲取剛剛那個快取,如下圖所示: 
快取弊端 
  所以我們為了解決這個問題,就得找一個分散式的快取,專門用來儲存快取資料的,這樣不同的伺服器要快取資料都往它那裡存,取快取資料也從它那裡取,如下圖所示: 
分散式快取 
  這樣就能解決上面所說的問題,為了提高系統併發效能、我們一般對系統進行上面這種分散式部署(叢集部署方式),所以要使用分散式快取對快取資料進行集中管理。但是mybatis無法實現分散式快取,需要和其它分散式快取框架進行整合,這裡主要介紹ehcache。

3.2 整合方法

  上文一開始提到過,mybatis提供了一個cache介面,如果要實現自己的快取邏輯,實現cache介面開發即可。mybatis本身預設實現了一個,但是這個快取的實現無法實現分散式快取,所以我們要自己來實現。ehcache分散式快取就可以,mybatis提供了一個針對cache介面的ehcache實現類,這個類在mybatis和ehcache的整合包中。所以首先我們需要匯入整合包(點我下載)。 
jar包 
  匯入了jar包後,配置mapper中cache中的type為ehcache對cache介面的實現型別。ehcache對cache介面有一個實現類為: 
實現類 
  我們將該類的完全限定名寫到type屬性中即可,如下: 
配置 
  OK,配置完成,現在mybatis就會自動去執行這個ehcache實現類了,就不會使用自己預設的二級快取了,但是使用ehcache還有一個快取配置別忘了,在classpath下新建一個ehcache.xml檔案:

<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
    xsi:noNamespaceSchemaLocation="../config/ehcache.xsd">

    <diskStore path="F:\develop\ehcache"/>

    <defaultCache
            maxElementsInMemory="10000"
            eternal="false"
            timeToIdleSeconds="120"
            timeToLiveSeconds="120"
            maxElementsOnDisk="10000000"
            diskExpiryThreadIntervalSeconds="120"
            memoryStoreEvictionPolicy="LRU">
        <persistence strategy="localTempSwap"/>
    </defaultCache>
</ehcache>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

  這裡面配置的作用跟hibernate差不多,大家可以去參考我那篇hibernate二級快取的博文。接下來就是測試了,還是用上面的那個測試程式,因為只改掉了快取,其他沒動。到此為止,mybatis的二級快取差不多就總結完了。

4. 二級快取的應用場景和侷限性

  對於訪問多的查詢請求且使用者對查詢結果實時性要求不高,此時可採用mybatis二級快取技術降低資料庫訪問量,提高訪問速度,業務場景比如:耗時較高的統計分析sql、電話賬單查詢sql等。實現方法如下:通過設定重新整理間隔時間,由mybatis每隔一段時間自動清空快取,根據資料變化頻率設定快取重新整理間隔flushInterval,比如設定為30分鐘、60分鐘、24小時等,根據需求而定。 
  mybatis二級快取對細粒度的資料級別的快取實現不好,比如如下需求:對商品資訊進行快取,由於商品資訊查詢訪問量大,但是要求使用者每次都能查詢最新的商品資訊,此時如果使用mybatis的二級快取就無法實現當一個商品變化時只重新整理該商品的快取資訊而不重新整理其它商品的資訊,因為mybaits的二級快取區域以mapper為單位劃分的,當一個商品資訊變化會將所有商品資訊的快取資料全部清空。解決此類問題可能需要在業務層根據需求對資料有針對性快取。