天天看點

mybatis 詳解(九)------ 一級緩存、二級緩存

mybatis 詳解(九)------一級緩存和二級緩存

  上一章節,我們講解了通過mybatis的懶加載來提高查詢效率,那麼除了懶加載,還有什麼方法能提高查詢效率呢?這就是我們本章講的緩存。

  本篇源碼下載下傳連結:http://pan.baidu.com/s/1eRHTsIm 密碼:a5wn

  mybatis 為我們提供了一級緩存和二級緩存,可以通過下圖來了解:

  

mybatis 詳解(九)------ 一級緩存、二級緩存

  ①、一級緩存是SqlSession級别的緩存。在操作資料庫時需要構造sqlSession對象,在對象中有一個資料結構(HashMap)用于存儲緩存資料。不同的sqlSession之間的緩存資料區域(HashMap)是互相不影響的。

  ②、二級緩存是mapper級别的緩存,多個SqlSession去操作同一個Mapper的sql語句,多個SqlSession可以共用二級緩存,二級緩存是跨SqlSession的。

1、一級緩存

  ①、我們在一個 sqlSession 中,對 User 表根據id進行兩次查詢,檢視他們發出sql語句的情況。

@Test
public void testSelectOrderAndUserByOrderId(){
	//根據 sqlSessionFactory 産生 session
	SqlSession sqlSession = sessionFactory.openSession();
	String statement = "one.to.one.mapper.OrdersMapper.selectOrderAndUserByOrderID";
	UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
	//第一次查詢,發出sql語句,并将查詢的結果放入緩存中
	User u1 = userMapper.selectUserByUserId(1);
	System.out.println(u1);
	
	//第二次查詢,由于是同一個sqlSession,會在緩存中查找查詢結果
	//如果有,則直接從緩存中取出來,不和資料庫進行互動
	User u2 = userMapper.selectUserByUserId(1);
	System.out.println(u2);
	
	sqlSession.close();
}
      

  檢視控制台列印情況:

mybatis 詳解(九)------ 一級緩存、二級緩存

  ②、 同樣是對user表進行兩次查詢,隻不過兩次查詢之間進行了一次update操作。

@Test
public void testSelectOrderAndUserByOrderId(){
	//根據 sqlSessionFactory 産生 session
	SqlSession sqlSession = sessionFactory.openSession();
	String statement = "one.to.one.mapper.OrdersMapper.selectOrderAndUserByOrderID";
	UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
	//第一次查詢,發出sql語句,并将查詢的結果放入緩存中
	User u1 = userMapper.selectUserByUserId(1);
	System.out.println(u1);
	
	//第二步進行了一次更新操作,sqlSession.commit()
	u1.setSex("女");
	userMapper.updateUserByUserId(u1);
	sqlSession.commit();
	
	//第二次查詢,由于是同一個sqlSession.commit(),會清空緩存資訊
	//則此次查詢也會發出 sql 語句
	User u2 = userMapper.selectUserByUserId(1);
	System.out.println(u2);
	
	sqlSession.close();
}
      

  控制台列印情況:

mybatis 詳解(九)------ 一級緩存、二級緩存

  ③、總結

  1、第一次發起查詢使用者id為1的使用者資訊,先去找緩存中是否有id為1的使用者資訊,如果沒有,從資料庫查詢使用者資訊。得到使用者資訊,将使用者資訊存儲到一級緩存中。 

  2、如果中間sqlSession去執行commit操作(執行插入、更新、删除),則會清空SqlSession中的一級緩存,這樣做的目的為了讓緩存中存儲的是最新的資訊,避免髒讀。

  3、第二次發起查詢使用者id為1的使用者資訊,先去找緩存中是否有id為1的使用者資訊,緩存中有,直接從緩存中擷取使用者資訊。

   

mybatis 詳解(九)------ 一級緩存、二級緩存

2、二級緩存

  二級緩存的原理和一級緩存原理一樣,第一次查詢,會将資料放入緩存中,然後第二次查詢則會直接去緩存中取。但是一級緩存是基于 sqlSession 的,而 二級緩存是基于 mapper檔案的namespace的,也就是說多個sqlSession可以共享一個mapper中的二級緩存區域,并且如果兩個mapper的namespace相同,即使是兩個mapper,那麼這兩個mapper中執行sql查詢到的資料也将存在相同的二級緩存區域中。

mybatis 詳解(九)------ 一級緩存、二級緩存

  那麼二級緩存是如何使用的呢?

  ①、開啟二級緩存

  和一級緩存預設開啟不一樣,二級緩存需要我們手動開啟

  首先在全局配置檔案 mybatis-configuration.xml 檔案中加入如下代碼:

<!--開啟二級緩存  -->
<settings>
	<setting name="cacheEnabled" value="true"/>
</settings>
      

  其次在 UserMapper.xml 檔案中開啟緩存

<!-- 開啟二級緩存 -->
<cache></cache>
      

  我們可以看到 mapper.xml 檔案中就這麼一個空标簽<cache/>,其實這裡可以配置<cache type="org.apache.ibatis.cache.impl.PerpetualCache"/>,PerpetualCache這個類是mybatis預設實作緩存功能的類。我們不寫type就使用mybatis預設的緩存,也可以去實作 Cache 接口來自定義緩存。

mybatis 詳解(九)------ 一級緩存、二級緩存
mybatis 詳解(九)------ 一級緩存、二級緩存

  我們可以看到 二級緩存 底層還是 HashMap 架構。

   ②、po 類實作 Serializable 序列化接口

mybatis 詳解(九)------ 一級緩存、二級緩存

   開啟了二級緩存後,還需要将要緩存的pojo實作Serializable接口,為了将緩存資料取出執行反序列化操作,因為二級緩存資料存儲媒體多種多樣,不一定隻存在記憶體中,有可能存在硬碟中,如果我們要再取這個緩存的話,就需要反序列化了。是以mybatis中的pojo都去實作Serializable接口。

   ③、測試

   一、測試二級緩存和sqlSession 無關

@Test
public void testTwoCache(){
	//根據 sqlSessionFactory 産生 session
	SqlSession sqlSession1 = sessionFactory.openSession();
	SqlSession sqlSession2 = sessionFactory.openSession();
	
	String statement = "com.ys.twocache.UserMapper.selectUserByUserId";
	UserMapper userMapper1 = sqlSession1.getMapper(UserMapper.class);
	UserMapper userMapper2 = sqlSession2.getMapper(UserMapper.class);
	//第一次查詢,發出sql語句,并将查詢的結果放入緩存中
	User u1 = userMapper1.selectUserByUserId(1);
	System.out.println(u1);
	sqlSession1.close();//第一次查詢完後關閉sqlSession
	
	//第二次查詢,即使sqlSession1已經關閉了,這次查詢依然不發出sql語句
	User u2 = userMapper2.selectUserByUserId(1);
	System.out.println(u2);
	sqlSession2.close();
}
      

  可以看出上面兩個不同的sqlSession,第一個關閉了,第二次查詢依然不發出sql查詢語句。

  二、測試執行 commit() 操作,二級緩存資料清空

@Test
public void testTwoCache(){
	//根據 sqlSessionFactory 産生 session
	SqlSession sqlSession1 = sessionFactory.openSession();
	SqlSession sqlSession2 = sessionFactory.openSession();
	SqlSession sqlSession3 = sessionFactory.openSession();
	
	String statement = "com.ys.twocache.UserMapper.selectUserByUserId";
	UserMapper userMapper1 = sqlSession1.getMapper(UserMapper.class);
	UserMapper userMapper2 = sqlSession2.getMapper(UserMapper.class);
	UserMapper userMapper3 = sqlSession2.getMapper(UserMapper.class);
	//第一次查詢,發出sql語句,并将查詢的結果放入緩存中
	User u1 = userMapper1.selectUserByUserId(1);
	System.out.println(u1);
	sqlSession1.close();//第一次查詢完後關閉sqlSession
	
	//執行更新操作,commit()
	u1.setUsername("aaa");
	userMapper3.updateUserByUserId(u1);
	sqlSession3.commit();
	
	//第二次查詢,由于上次更新操作,緩存資料已經清空(防止資料髒讀),這裡必須再次發出sql語句
	User u2 = userMapper2.selectUserByUserId(1);
	System.out.println(u2);
	sqlSession2.close();
}
      

  檢視控制台情況:

mybatis 詳解(九)------ 一級緩存、二級緩存

   ④、useCache和flushCache

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

<select id="selectUserByUserId" useCache="false" resultType="com.ys.twocache.User" parameterType="int">
	select * from user where id=#{id}
</select>
      

  這種情況是針對每次查詢都需要最新的資料sql,要設定成useCache=false,禁用二級緩存,直接從資料庫中擷取。 

  在mapper的同一個namespace中,如果有其它insert、update、delete操作資料後需要重新整理緩存,如果不執行重新整理緩存會出現髒讀。

    設定statement配置中的flushCache=”true” 屬性,預設情況下為true,即重新整理緩存,如果改成false則不會重新整理。使用緩存時如果手動修改資料庫表中的查詢資料會出現髒讀。

<select id="selectUserByUserId" flushCache="true" useCache="false" resultType="com.ys.twocache.User" parameterType="int">
	select * from user where id=#{id}
</select>
      

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

3、二級緩存整合ehcache

  上面我們介紹了mybatis自帶的二級緩存,但是這個緩存是單伺服器工作,無法實作分布式緩存。那麼什麼是分布式緩存呢?假設現在有兩個伺服器1和2,使用者通路的時候通路了1伺服器,查詢後的緩存就會放在1伺服器上,假設現在有個使用者通路的是2伺服器,那麼他在2伺服器上就無法擷取剛剛那個緩存,如下圖所示:

mybatis 詳解(九)------ 一級緩存、二級緩存

  為了解決這個問題,就得找一個分布式的緩存,專門用來存儲緩存資料的,這樣不同的伺服器要緩存資料都往它那裡存,取緩存資料也從它那裡取,如下圖所示:

mybatis 詳解(九)------ 一級緩存、二級緩存

   如上圖所示,在幾個不同的伺服器之間,我們使用第三方緩存架構,将緩存都放在這個第三方架構中,然後無論有多少台伺服器,我們都能從緩存中擷取資料。

  這裡我們介紹mybatis與第三方架構ehcache的整合。

  上文一開始提到過,mybatis提供了一個cache接口,如果要實作自己的緩存邏輯,實作cache接口開發即可。mybatis本身預設實作了一個,但是這個緩存的實作無法實作分布式緩存,是以我們要自己來實作。ehcache分布式緩存就可以,mybatis提供了一個針對cache接口的ehcache實作類,這個類在mybatis和ehcache的整合包中。

  ①、導入 mybatis-ehcache 整合包(最上面的源代碼中包含有)

mybatis 詳解(九)------ 一級緩存、二級緩存

   ②、在全局配置檔案 mybatis-configuration.xml 開啟緩存

<!--開啟二級緩存  -->
<settings>
	<setting name="cacheEnabled" value="true"/>
</settings>
      

   ③、在 xxxMapper.xml 檔案中整合 ehcache 緩存

  将如下的類的全類名寫入<cache type="" ></cache>的type屬性中

<!-- 開啟本mapper的namespace下的二級緩存 
	type:指定cache接口的實作類的類型,不寫type屬性,mybatis預設使用PerpetualCache
	要和ehcache整合,需要配置type為ehcache實作cache接口的類型
-->
<cache type="org.mybatis.caches.ehcache.EhcacheCache"></cache>
      

  ④、配置緩存參數

  在 classpath 目錄下建立一個 ehcache.xml 檔案,并增加如下配置:

<?xml version="1.0" encoding="UTF-8"?>
<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>
      

 diskStore:指定資料在磁盤中的存儲位置。

 defaultCache:當借助CacheManager.add("demoCache")建立Cache時,EhCache便會采用<defalutCache/>指定的的管理政策

以下屬性是必須的:

 maxElementsInMemory - 在記憶體中緩存的element的最大數目

 maxElementsOnDisk - 在磁盤上緩存的element的最大數目,若是0表示無窮大

 eternal - 設定緩存的elements是否永遠不過期。如果為true,則緩存的資料始終有效,如果為false那麼還要根據timeToIdleSeconds,timeToLiveSeconds判斷

 overflowToDisk - 設定當記憶體緩存溢出的時候是否将過期的element緩存到磁盤上

以下屬性是可選的:

 timeToIdleSeconds - 當緩存在EhCache中的資料前後兩次通路的時間超過timeToIdleSeconds的屬性取值時,這些資料便會删除,預設值是0,也就是可閑置時間無窮大

 timeToLiveSeconds - 緩存element的有效生命期,預設是0.,也就是element存活時間無窮大

 diskSpoolBufferSizeMB 這個參數設定DiskStore(磁盤緩存)的緩存區大小.預設是30MB.每個Cache都應該有自己的一個緩沖區.

 diskPersistent - 在VM重新開機的時候是否啟用磁盤儲存EhCache中的資料,預設是false。

 diskExpiryThreadIntervalSeconds - 磁盤緩存的清理線程運作間隔,預設是120秒。每個120s,相應的線程會進行一次EhCache中資料的清理工作

 memoryStoreEvictionPolicy - 當記憶體緩存達到最大,有新的element加入的時候, 移除緩存中element的政策。預設是LRU(最近最少使用),可選的有LFU(最不常使用)和FIFO(先進先出)

4、二級緩存的應用場景

  對于通路多的查詢請求且使用者對查詢結果實時性要求不高,此時可采用mybatis二級緩存技術降低資料庫通路量,提高通路速度,業務場景比如:耗時較高的統計分析sql、電話賬單查詢sql等。實作方法如下:通過設定重新整理間隔時間,由mybatis每隔一段時間自動清空緩存,根據資料變化頻率設定緩存重新整理間隔flushInterval,比如設定為30分鐘、60分鐘、24小時等,根據需求而定。 

  mybatis二級緩存對細粒度的資料級别的緩存實作不好,比如如下需求:對商品資訊進行緩存,由于商品資訊查詢通路量大,但是要求使用者每次都能查詢最新的商品資訊,此時如果使用mybatis的二級緩存就無法實作當一個商品變化時隻重新整理該商品的緩存資訊而不重新整理其它商品的資訊,因為mybaits的二級緩存區域以mapper為機關劃分的,當一個商品資訊變化會将所有商品資訊的緩存資料全部清空。解決此類問題可能需要在業務層根據需求對資料有針對性緩存。

作者:IT可樂

出處:http://www.cnblogs.com/ysocean/

資源:微信搜【IT可樂】關注我,回複 【電子書】有我特别篩選的免費電子書。

本文版權歸作者所有,歡迎轉載,但未經作者同意不能轉載,否則保留追究法律責任的權利。