MyBaties的二級緩存

二級緩存介紹

在上文中提到的一級緩存中,其最大的共享範圍就是一個SqlSession內部,那麼如何讓多個SqlSession之間也能夠共享緩存呢,答案是二級緩存。
當開啓二級緩存後,會使用CachingExecutor裝飾Executor,在進入後續執行前,先在CachingExecutor進行二級緩存的查詢,具體的工做流程以下所示。sql

在二級緩存的使用中,一個namespace下的全部操做語句,都影響着同一個Cache,即二級緩存是被多個SqlSession共享着的,是一個全局的變量。
當開啓緩存後,數據的查詢執行的流程就是 二級緩存 -> 一級緩存 -> 數據庫。數據庫

二級緩存配置

要正確的使用二級緩存,需完成以下配置的。
1 在Mybatis的配置文件中開啓二級緩存。緩存

<setting name="cacheEnabled" value="true"/>安全

2 在Mybatis的映射XML中配置cache或者 cache-ref 。session

<cache/>app

<cache-ref namespace="mapper.StudentMapper"/>框架

cache-ref表明引用別的命名空間的Cache配置,兩個命名空間的操做使用的是同一個Cache。分佈式

二級緩存實驗

實驗1測試

測試二級緩存效果,不提交事務,sqlSession1查詢完數據後,sqlSession2相同的查詢是否會從緩存中獲取數據。spa

@Test
public void testCacheWithoutCommitOrClose() 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("studentMapper2讀取數據: " + studentMapper2.getStudentById(1));
}

執行結果:

 

咱們能夠看到,當sqlsession沒有調用commit()方法時,二級緩存並無起到做用。

實驗2

測試二級緩存效果,當提交事務時,sqlSession1查詢完數據後,sqlSession2相同的查詢是否會從緩存中獲取數據。

@Test
public void testCacheWithCommitOrClose() 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));
        sqlSession1.commit();
        System.out.println("studentMapper2讀取數據: " + studentMapper2.getStudentById(1));
}

 

從圖上可知,sqlsession2的查詢,使用了緩存,緩存的命中率是0.5。

實驗3

測試update操做是否會刷新該namespace下的二級緩存。

@Test
public void testCacheWithUpdate() throws Exception {
        SqlSession sqlSession1 = factory.openSession(true); 
        SqlSession sqlSession2 = factory.openSession(true); 
        SqlSession sqlSession3 = factory.openSession(true); 
        
        StudentMapper studentMapper = sqlSession1.getMapper(StudentMapper.class);
        StudentMapper studentMapper2 = sqlSession2.getMapper(StudentMapper.class);
        StudentMapper studentMapper3 = sqlSession3.getMapper(StudentMapper.class);
        
        System.out.println("studentMapper讀取數據: " + studentMapper.getStudentById(1));
        sqlSession1.commit();
        System.out.println("studentMapper2讀取數據: " + studentMapper2.getStudentById(1));
        
        studentMapper3.updateStudentName("方方",1);
        sqlSession3.commit();
        System.out.println("studentMapper2讀取數據: " + studentMapper2.getStudentById(1));
}

 

咱們能夠看到,在sqlSession3更新數據庫,並提交事務後,sqlsession2的StudentMapper namespace下的查詢走了數據庫,沒有走Cache。

實驗4

驗證Mybatis的二級緩存不適應用於映射文件中存在多表查詢的狀況。通常來講,咱們會爲每個單表建立一個單獨的映射文件,若是存在涉及多個表的查詢的話,因爲Mybatis的二級緩存是基於namespace的,多表查詢語句所在的namspace沒法感應到其餘namespace中的語句對多表查詢中涉及的表進行了修改,引起髒數據問題。

@Test
public void testCacheWithDiffererntNamespace() throws Exception {
        SqlSession sqlSession1 = factory.openSession(true); 
        SqlSession sqlSession2 = factory.openSession(true); 
        SqlSession sqlSession3 = factory.openSession(true); 
    
        StudentMapper studentMapper = sqlSession1.getMapper(StudentMapper.class);
        StudentMapper studentMapper2 = sqlSession2.getMapper(StudentMapper.class);
        ClassMapper classMapper = sqlSession3.getMapper(ClassMapper.class);
        
        System.out.println("studentMapper讀取數據: " + studentMapper.getStudentByIdWithClassInfo(1));
        sqlSession1.close();
        System.out.println("studentMapper2讀取數據: " + studentMapper2.getStudentByIdWithClassInfo(1));

        classMapper.updateClassName("特點一班",1);
        sqlSession3.commit();
        System.out.println("studentMapper2讀取數據: " + studentMapper2.getStudentByIdWithClassInfo(1));
}

執行結果:

 

在這個實驗中,咱們引入了兩張新的表,一張class,一張classroom。class中保存了班級的id和班級名,classroom中保存了班級id和學生id。咱們在StudentMapper中增長了一個查詢方法getStudentByIdWithClassInfo,用於查詢學生所在的班級,涉及到多表查詢。在ClassMapper中添加了updateClassName,根據班級id更新班級名的操做。當sqlsession1的studentmapper查詢數據後,二級緩存生效。保存在StudentMapper的namespace下的cache中。當sqlSession3的classMapper的updateClassName方法對class表進行更新時,updateClassName不屬於StudentMapper的namespace,因此StudentMapper下的cache沒有感應到變化,沒有刷新緩存。當StudentMapper中一樣的查詢再次發起時,從緩存中讀取了髒數據。

實驗5

爲了解決實驗4的問題呢,可使用Cache ref,讓ClassMapper引用StudenMapper命名空間,這樣兩個映射文件對應的Sql操做都使用的是同一塊緩存了。
執行結果:

 

不過這樣作的後果是,緩存的粒度變粗了,多個Mapper namespace下的全部操做都會對緩存使用形成影響,其實這個緩存存在的意義已經不大了。

總結

  1. Mybatis的二級緩存相對於一級緩存來講,實現了SqlSession之間緩存數據的共享,同時粒度更加的細,可以到Mapper級別,經過Cache接口實現類不一樣的組合,對Cache的可控性也更強。
  2. Mybatis在多表查詢時,極大可能會出現髒數據,有設計上的缺陷,安全使用的條件比較苛刻。
  3. 在分佈式環境下,因爲默認的Mybatis Cache實現都是基於本地的,分佈式環境下必然會出現讀取到髒數據,須要使用集中式緩存將Mybatis的Cache接口實現,有必定的開發成本,不如直接用Redis,Memcache實現業務上的緩存就行了
  4. 最終的結論是Mybatis的緩存機制設計的不是很完善,在使用上容易引發髒數據問題,我的建議不要使用Mybatis緩存,在業務層面上使用其餘機制實現須要的緩存功能,讓Mybatis老老實實作它的ORM框架就行了。

 

文章參考:https://www.jianshu.com/p/c553169c5921

相關文章
相關標籤/搜索