上一章節,咱們講解了經過mybatis的懶加載來提升查詢效率,那麼除了懶加載,還有什麼方法能提升查詢效率呢?這就是咱們本章講的緩存。java
本篇源碼下載連接:http://pan.baidu.com/s/1eRHTsIm 密碼:a5wnsql
mybatis 爲咱們提供了一級緩存和二級緩存,能夠經過下圖來理解:數據庫
①、一級緩存是SqlSession級別的緩存。在操做數據庫時須要構造sqlSession對象,在對象中有一個數據結構(HashMap)用於存儲緩存數據。不一樣的sqlSession之間的緩存數據區域(HashMap)是互相不影響的。apache
②、二級緩存是mapper級別的緩存,多個SqlSession去操做同一個Mapper的sql語句,多個SqlSession能夠共用二級緩存,二級緩存是跨SqlSession的。緩存
①、咱們在一個 sqlSession 中,對 User 表根據id進行兩次查詢,查看他們發出sql語句的狀況。服務器
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
@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();
}
|
查看控制檯打印狀況:session
②、 一樣是對user表進行兩次查詢,只不過兩次查詢之間進行了一次update操做。數據結構
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
@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
③、總結架構
一、第一次發起查詢用戶id爲1的用戶信息,先去找緩存中是否有id爲1的用戶信息,若是沒有,從數據庫查詢用戶信息。獲得用戶信息,將用戶信息存儲到一級緩存中。
二、若是中間sqlSession去執行commit操做(執行插入、更新、刪除),則會清空SqlSession中的一級緩存,這樣作的目的爲了讓緩存中存儲的是最新的信息,避免髒讀。
三、第二次發起查詢用戶id爲1的用戶信息,先去找緩存中是否有id爲1的用戶信息,緩存中有,直接從緩存中獲取用戶信息。
二級緩存的原理和一級緩存原理同樣,第一次查詢,會將數據放入緩存中,而後第二次查詢則會直接去緩存中取。可是一級緩存是基於 sqlSession 的,而 二級緩存是基於 mapper文件的namespace的,也就是說多個sqlSession能夠共享一個mapper中的二級緩存區域,而且若是兩個mapper的namespace相同,即便是兩個mapper,那麼這兩個mapper中執行sql查詢到的數據也將存在相同的二級緩存區域中。
那麼二級緩存是如何使用的呢?
①、開啓二級緩存
和一級緩存默認開啓不同,二級緩存須要咱們手動開啓
首先在全局配置文件 mybatis-configuration.xml 文件中加入以下代碼:
1
2
3
4
|
<!--開啓二級緩存 -->
<settings>
<setting name=
"cacheEnabled"
value=
"true"
/>
</settings>
|
其次在 UserMapper.xml 文件中開啓緩存
1
2
|
<!-- 開啓二級緩存 -->
<cache></cache>
|
咱們能夠看到 mapper.xml 文件中就這麼一個空標籤<cache/>,其實這裏能夠配置<cache type="org.apache.ibatis.cache.impl.PerpetualCache"/>,PerpetualCache這個類是mybatis默認實現緩存功能的類。咱們不寫type就使用mybatis默認的緩存,也能夠去實現 Cache 接口來自定義緩存。
咱們能夠看到 二級緩存 底層仍是 HashMap 架構。
②、po 類實現 Serializable 序列化接口
開啓了二級緩存後,還須要將要緩存的pojo實現Serializable接口,爲了將緩存數據取出執行反序列化操做,由於二級緩存數據存儲介質多種多樣,不必定只存在內存中,有可能存在硬盤中,若是咱們要再取這個緩存的話,就須要反序列化了。因此mybatis中的pojo都去實現Serializable接口。
③、測試
1、測試二級緩存和sqlSession 無關
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
@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查詢語句。
2、測試執行 commit() 操做,二級緩存數據清空
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
|
@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();
}
|
查看控制檯狀況:
④、useCache和flushCache
mybatis中還能夠配置userCache和flushCache等配置項,userCache是用來設置是否禁用二級緩存的,在statement中設置useCache=false能夠禁用當前select語句的二級緩存,即每次查詢都會發出sql去查詢,默認狀況是true,即該sql使用二級緩存。
1
2
3
|
<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則不會刷新。使用緩存時若是手動修改數據庫表中的查詢數據會出現髒讀。
1
2
3
|
<select id=
"selectUserByUserId"
flushCache=
"true"
useCache=
"false"
resultType=
"com.ys.twocache.User"
parameterType=
"int"
>
select * from user where id=#{id}
</select>
|
通常下執行完commit操做都須要刷新緩存,flushCache=true表示刷新緩存,這樣能夠避免數據庫髒讀。因此咱們不用設置,默認便可。
上面咱們介紹了mybatis自帶的二級緩存,可是這個緩存是單服務器工做,沒法實現分佈式緩存。那麼什麼是分佈式緩存呢?假設如今有兩個服務器1和2,用戶訪問的時候訪問了1服務器,查詢後的緩存就會放在1服務器上,假設如今有個用戶訪問的是2服務器,那麼他在2服務器上就沒法獲取剛剛那個緩存,以下圖所示:
爲了解決這個問題,就得找一個分佈式的緩存,專門用來存儲緩存數據的,這樣不一樣的服務器要緩存數據都往它那裏存,取緩存數據也從它那裏取,以下圖所示:
如上圖所示,在幾個不一樣的服務器之間,咱們使用第三方緩存框架,將緩存都放在這個第三方框架中,而後不管有多少臺服務器,咱們都能從緩存中獲取數據。
這裏咱們介紹mybatis與第三方框架ehcache的整合。
上文一開始提到過,mybatis提供了一個cache接口,若是要實現本身的緩存邏輯,實現cache接口開發便可。mybatis自己默認實現了一個,可是這個緩存的實現沒法實現分佈式緩存,因此咱們要本身來實現。ehcache分佈式緩存就能夠,mybatis提供了一個針對cache接口的ehcache實現類,這個類在mybatis和ehcache的整合包中。
①、導入 mybatis-ehcache 整合包(最上面的源代碼中包含有)
②、在全局配置文件 mybatis-configuration.xml 開啓緩存
1
2
3
4
|
<!--開啓二級緩存 -->
<settings>
<setting name=
"cacheEnabled"
value=
"true"
/>
</settings>
|
③、在 xxxMapper.xml 文件中整合 ehcache 緩存
將以下的類的全類名寫入<cache type="" ></cache>的type屬性中
1
2
3
4
5
|
<!-- 開啓本mapper的namespace下的二級緩存
type:指定cache接口的實現類的類型,不寫type屬性,mybatis默認使用PerpetualCache
要和ehcache整合,須要配置type爲ehcache實現cache接口的類型
-->
<cache type=
"org.mybatis.caches.ehcache.EhcacheCache"
></cache>
|
④、配置緩存參數
在 classpath 目錄下新建一個 ehcache.xml 文件,並增長以下配置:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
<?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(先進先出)
對於訪問多的查詢請求且用戶對查詢結果實時性要求不高,此時可採用mybatis二級緩存技術下降數據庫訪問量,提升訪問速度,業務場景好比:耗時較高的統計分析sql、電話帳單查詢sql等。實現方法以下:經過設置刷新間隔時間,由mybatis每隔一段時間自動清空緩存,根據數據變化頻率設置緩存刷新間隔flushInterval,好比設置爲30分鐘、60分鐘、24小時等,根據需求而定。
mybatis二級緩存對細粒度的數據級別的緩存實現很差,好比以下需求:對商品信息進行緩存,因爲商品信息查詢訪問量大,可是要求用戶每次都能查詢最新的商品信息,此時若是使用mybatis的二級緩存就沒法實現當一個商品變化時只刷新該商品的緩存信息而不刷新其它商品的信息,由於mybaits的二級緩存區域以mapper爲單位劃分的,當一個商品信息變化會將全部商品信息的緩存數據所有清空。解決此類問題可能須要在業務層根據需求對數據有針對性緩存。