Mybatis緩存機制:
我們一般都會使用mybatis的默認緩存配置,但是mybatis的緩存機制有不足之處,使用中可能會造成髒數據問題。
Mybatis一級緩存:
mybatis一級緩存是sqlsession級別的。一級緩存的作用域是一個sqlsession。mbatis默認開啓一級緩存 。
在同一個sqlsession中,執行相同的查詢sql。第一次會去查詢數據庫,並寫道緩存中,第二次直接從緩存中讀取。若對應數據發生增刪改操作,則緩存失效。
一級緩存是以sqlsession爲單位進行劃分的,若找不到在去數據庫查詢,然後將結果寫到緩存中。mybatis內部使用HashMap ,key爲hashcode+statementid+sql語句。value爲查詢出來的結果集映射成的java對象。
一級緩存帶來的髒數據問題:
當使用兩個或者兩個以上的sqlsession時,由於緩存數據是sqlsession內共享的,當另一個sqlsession進行增刪改操作時,其它sqlsession時感知不到,因此其此時緩存中的數據編程髒數據。
@Test
public void testLocalCacheScope() throws Exception {
SqlSession sqlSession1 = factory.openSession(true);
SqlSession sqlSession2 = factory.openSession(true);
StudentMapper studentMapper = sqlSession1.getMapper(StudentMapper.class);
StudentMapper studentMapper2 = sqlSession2.getMapper(StudentMapper.class);
System.out.println("studentMapper讀取數據: " + studentMapper.getStudentById(1));
System.out.println("studentMapper讀取數據: " + studentMapper.getStudentById(1));
System.out.println("studentMapper2更新了" + studentMapper2.updateStudentName("小岑",1) + "個學生的數據");
System.out.println("studentMapper讀取數據: " + studentMapper.getStudentById(1));
System.out.println("studentMapper2讀取數據: " + studentMapper2.getStudentById(1));
}
一級緩存總結:
- mybatis一級緩存聲明週期和sqlsession一致。
- mybatis緩存內部設計簡單,只有一個沒有容量限制的hashmap,緩存性能有欠缺/
- mybatis一級緩存的最大範圍是sqlsession內部,在多個sqlsession或者分佈式環境下,數據庫寫操作會引起髒數據問題
Mybatis二級緩存:
mybatis二級緩存指mapper映射文件。二級緩存的作用域是同一個namespace(命名空間)下的mapper映射文件內容。多個sqlsession共享。
Mybatis需要手動設置二級緩存
同一個namespace下的mapper文件中,執行相同的sql查詢語句,第一次會讀取數據庫,並寫道緩存中,若中間沒有進行增刪改操作,則從緩存讀取查詢結果。
配置二級緩存:
- 在全局配置文件中進行如下配置
<settings>
<!-- 打開延遲加載的開關 -->
<setting name="lazyLoadingEnabled" value="true"/>
<!-- 將積極加載改爲消極加載,即延遲加載 -->
<setting name="aggressiveLazyLoading" value="false"/>
<!--開啓二級緩存-->
<setting name="cacheEnabled" value="true"/>
</settings>
- 在對應mapper下進行如下配置:
- cache可配置參數
type:cache使用的類型,默認是PerpetualCache,這在一級緩存中提到過。
eviction: 定義回收的策略,常見的有FIFO,LRU。
flushInterval: 配置一定時間自動刷新緩存,單位是毫秒。
size: 最多緩存對象的個數。
readOnly: 是否只讀,若配置可讀寫,則需要對應的實體類能夠序列化。
blocking: 若緩存中找不到對應的key,是否會一直blocking,直到有對應的數據進入緩存。
-
映射的java對象實現序列化
-
映射文件對應的需要開啓二級緩存的namespace進行如下設置
<cache eviction="LRU" flushInterval="60000" size="512" readOnly="true"></cache>
<!--cache-ref 代表引用別的namespace中的Cache配置,兩個namespace使用同一個Cache-->
<cache-ref namespace="mapper.StudentMapper"/>
//一二級緩存均有用
select 標籤下:user userCache標籤 是否使用緩存
update/selete/update 標籤下 : flushCache標籤 刷新緩存
注意: MyBatis的二級緩存不適應用於映射文件中存在多表查詢的情況。
通常我們會爲每個單表創建單獨的映射文件,由於MyBatis的二級緩存是基於namespace的,多表查詢語句所在的namspace無法感應到其他namespace中的語句對多表查詢中涉及的表進行的修改,引發髒數據問題。
可以使用Cache-ref,這樣兩個映射文件對應的Sql操作都使用的是同一塊緩存了。
不過這樣做的後果是,緩存的粒度變粗了,多個Mapper namespace下的所有操作都會對緩存使用造成影響。
二級緩存總結:
- 二級緩存相對於一級緩存來說,實現了sqlsession間的緩存數據共享,粒度到namespace級別,
- mybatis在進行多表查詢時,極大可能會出現髒數據,有設計上缺陷,安全使用二級緩存的條件比較苛刻。
- 在分佈式環境下,由於默認的mybatis cache都是基於本地的,分佈式環境下必然會出現髒數據問題(多個server服務器都會進行緩存,且互不通知數據更新),需要使用集中式緩存將mybatis的cache接口實現,有一定的開發成本。相比較下,直接使用redis等分佈式緩存可能成本更低,安全性更高。