在上文中提到的一級緩存中,其最大的共享範圍就是一個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下的全部操做都會對緩存使用形成影響,其實這個緩存存在的意義已經不大了。
文章參考:https://www.jianshu.com/p/c553169c5921