查詢緩存
mybaits提供了一級緩存和二級緩存,用于減輕數據壓力,提高數據庫性能。。
1.1 什么是一級緩存
我們創建出一個SqlSession對象表示一次數據庫會話,在這次會話中,我們有可能好幾次執行一樣的查詢語句,若是每次都去訪問數據庫,那么就會造成數據庫資源的浪費,所以mybatis引入一級緩存,在SqlSession中建立一個緩存(數據結構為hashmap),每次查詢先去查看緩存中是否有,有的話就把結果返回,沒有的話再去查詢數據庫并將查到的結果緩存起來。
需要注意的是,不同的sqlSession對象之間的緩存數據區域是互相不影響的。
SqlSession只是對外的接口,真正執行各種數據庫操作的是Executor執行器(也是一個接口)。當創建了一個SqlSession對象時,MyBatis會為這個SqlSession對象創建一個新的Executor執行器,而緩存信息就被維護在這個Executor執行器中。MyBatis將緩存和對緩存相關的操作封裝成了Cache接口中。
SqlSession、Executor、Cache之間的關系如下列類圖所示:
sqlSession級別的一級緩存實際上就是使用PerpetualCache維護的。
1.2一級緩存的生命周期
開啟一個數據庫會話時,會創建一個新的SqlSession對象,SqlSession對象中會有一個新的Executor對象,Executor對象中持有一個新的PerpetualCache對象;當會話結束時,SqlSession對象及其內部的Executor對象還有PerpetualCache對象也一并釋放掉;
如果SqlSession調用了close()方法,會釋放掉一級緩存PerpetualCache對象,此時一級緩存不可用;
如果SqlSession調用了clearCache(),會清空PerpetualCache對象中的數據,但是PerpetualCache對象仍可使用;
4.SqlSession中執行了一個更新增加刪除操作 ,都會清空全部PerpetualCache對象的數據,但是該對象可以繼續使用;
1.3 一級緩存的工作流程
對于某個查詢,根據statementId,params,rowBounds來構建一個key值;
根據這個key值去緩存Cache中判斷是否命中緩存;
如果命中,則直接將緩存結果返回;
-
如果沒命中:
4.1 去數據庫中查詢數據,得到查詢結果; 4.2 將key和查詢到的結果分別作為key,value對存儲到Cache中; 4.3. 將查詢結果返回;
結束。
1.4 如何判斷兩次查詢是一樣的
從實現來看,mybatis是利用key是否相同來判斷兩次查詢是否相同的,而key是由statementId,params,rowBounds這三項構建的。
其中,statementId決定帶參數的sql(有“?”占位符的的sql或是需要拼接字符串的sql語句),params決定占位符傳入的參數或是要拼接的字符串,rowBounds決定查詢的結果集的范圍(也就是 limit x,y ),這三個參數決定了最終的sql,也就是說當這個最終的sql一樣時,那么就判斷這兩次是一樣查詢。
1.5 真正應用
真正開發的時候,sqlSession是用spring管理的,而mapper是在service里面調用,在這種情況下,什么時候會用到一級緩存呢?
實際上,在一個service里,只有一個sqlSession,在service結束之后才會關閉這個sqlSession,sqlSession關閉則緩存清空。所以一個service函數里面多次調用mapper是會走一級緩存的,而多次調用service則互不影響。
2.1 什么是二級緩存
二級緩存是namespace級別的緩存,多個SqlSession可以共用二級緩存,不同sqlSession操作同一個namespace下的同一個sql,就會走二級緩存。
2.2開啟二級緩存
在SqlMapConfig.xml里開啟mybatis的全局二級緩存開關,默認是開啟的:
<settings>
<setting name="cacheEnabled" value="true"/>
</settings>
還需要在要二級緩存開啟的mapper.xml里面開啟開關:
<cache/>
2.3 二級緩存與一級緩存區別
二級緩存與一級緩存區別,二級緩存的范圍更大,多個sqlSession可以共享一個namespace下的二級緩存區域。
2.4 測試二級緩存
需要關閉sqlSession1才會把內容寫到緩存,sqlSession2才會讀到:
在sqlSession1和sqlSession2之間加入以下代碼,為了測試更新操作是否清空緩存:
2.5 二級緩存參數設置
設置某些sql禁用二級緩存
在statement中設置useCache=false可以禁用當前select語句使用二級緩存,即每次查詢都會發出sql去查詢數據庫。默認情況是true,即該sql使用二級緩存。
<select id="findOrderListResultMap" resultMap="ordersUserMap" useCache="false">
只有每次查詢都需要最新的數據sql,要設置成useCache=false,才禁用二級緩存。
刷新緩存操作
刷新就是清空緩存,在insert update delete等statement中設置flushCache="true"(默認就是true),進行這些操作后會清空緩存,可以避免臟讀的情況。
刷新間隔參數
默認是沒有刷新間隔,刷新只在調用語句時刷新(這里的刷新都是指清空)
引用數目屬性
size:可被設置成任意正整數,默認值是1024
readOnly屬性
可被設置成true或是false,默認是false。只讀為true的緩存的對象實例是同一個;只讀為false的緩存返回的對象實例是拷貝的(通過序列化,所以配置成false需要pojo實現序列化接口),慢但是安全。
一個設置參數的例子
<cache eviction="FIFO" flushInterval="60 000" size="512" readOnly="true"/>
這個配置了一個FIFO緩存;并每隔60秒(60 000ms)刷新;存儲結果對象或結果列表的引用數是512個;返回的結果是只讀的,因此在不同線程中調用修改它們會沖突。可用的回收策略默認是LRU。
2.6 二級緩存應用場景
單表查詢時,對于實時性要求不高的請求,可以采用二級緩存并設置緩存刷新間隔來減少數據庫訪問量,提高訪問速度。
2.7 mybatis二級緩存局限性
最致命的是,多表查詢的情況出現的問題,比如我做訂單和用戶的多表查詢,這個statement可以寫在UserMapper.xml或是OrderMapper.xml或者是另創一個xml,假設我把這個statement放進OrderMapper.xml,我多表查詢了一次,結果被緩存下來,那么這時候我再去修改user的信息,可是這個更新操作是UserMapper.xml,我們使用mybatis的逆向工程,這兩個xml的namespace是不一樣的,也就是說這次更新操作沒有清空OrderMapper的緩存,那么我下次多表查詢就是查到錯誤的數據。
解決方法:
可以加入<cache-ref namespace="mapper.StudentMapper"/>
配置,cache-ref代表引用別的命名空間的Cache配置,兩個命名空間的操作使用的是同一個Cache。
也可以是設計一個攔截器,判斷涉及到的表,進行更新操作,把有關的表的緩存都清空,不過這樣效率有點低。
mybatis的二級緩存默認是本地的,分布式環境下可能會有過時緩存數據被讀取的情況,所以還是放棄mybatis的二級緩存,選擇在業務層引入可控制的緩存替代比較好,比如redis等等。
3. ehcache
ehcache可以對頁面、對象、數據進行緩存,同時支持集群/分布式緩存。
怎么整合mybatis
在核心配置文件SqlMapConfig.xml開啟mybatis的二級緩存,默認是開啟的
導入ehcache相關jar包
- ehcache-core-2.6.5.jar
- mybatis-ehcache-1.0.2.jar
-
在classpath下加入ehcache.xml文件
-
在XXXMapper.xml中開啟二緩存,XXXMapper.xml下的sql執行完成會存儲到它的緩存區域(HashMap)