Mybatis一級、二級緩存

轉載自https://blog.csdn.net/u011403655/article/details/46696065

一級緩存

首先做一個測試,創建一個mapper配置文件和mapper接口,我這裏用了最簡單的查詢來演示。

<mapper namespace="cn.elinzhou.mybatisTest.mapper.UserMapper">

    <select id="findUsers" resultType="cn.elinzhou.mybatisTest.pojo.User">
        SELECT * FROM user
    </select>
</mapper>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
public interface UserMapper {
    List<User> findUsers()throws Exception;
}
  • 1
  • 2
  • 3

然後編寫一個單元測試

public class UserMapperTest {


    SqlSession sqlSession = null;
    @Before
    public void setUp() throws Exception {
        // 通過配置文件獲取數據庫連接信息
        Reader reader = Resources.getResourceAsReader("cn/elinzhou/mybatisTest/config/mybatis.xml");
        // 通過配置信息構建一個SqlSessionFactory
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
        // 通過sqlSessionFactory打開一個數據庫會話
        sqlSession = sqlSessionFactory.openSession();
    }

    @Test
    public void testFindUsers() throws Exception {
        UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
        List<User> users = userMapper.findUsers();
        System.out.println(users);
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

運行,可以看到控制檯輸出(先配好log4j)爲類似如下圖日誌
這裏寫圖片描述

日誌說明了該操作執行的sql語句已經查詢的內容,最後一行是我手動通過System.out.printf輸出的結果。

然後再加一條語句

users = userMapper.findUsers();
  • 1

之前的單元測試就變成了這個樣子

這裏寫圖片描述

也就是在執行完userMapper.findUsers();後立刻再執行一遍userMapper.findUsers(); 可以想象,其實這兩個操作執行的sql是完全相同的,而且在這期間沒有對數據庫進行過其他操作。然後執行該單元測試,發現效果跟上面執行一條的時候完全相同,也就是執行第二次userMapper.findUsers();操作的時候沒有對數據庫進行查詢,那麼得到的數據是從哪裏來的?答案是一級緩存。

mybatis一級緩存是指在內存中開闢一塊區域,用來保存用戶對數據庫的操作信息(sql)和數據庫返回的數據,如果下一次用戶再執行相同的請求,那麼直接從內存中讀數數據而不是從數據庫讀取。
其中數據的生命週期有兩個影響因素。

  1. 對sqlsession執行commit操作時

對sqlsession執行commit操作,也就意味着用戶執行了update、delete等操作,那麼數據庫中的數據勢必會發生變化,如果用戶請求數據仍然使用之前內存中的數據,那麼將讀到髒數據。所以在執行sqlsession操作後,會清除保存數據的HashMap,用戶在發起查詢請求時就會重新讀取數據並放入一級緩存中了。

這裏寫圖片描述

這裏寫圖片描述

上述測試就是在第一查詢完後執行了commit操作,再進行查詢。與之前的測試不同的是,這次測試控制檯打印了兩組查詢結果,說明在commit之後mybatis對數據重新進行了查詢。

  1. 關閉sqlsession

一般在mybatis集成spring時,會把SqlSessionFactory設置爲單例注入到IOC容器中,不把sqlsession也設置爲單例的原因是sqlsession是線程不安全的,所以不能爲單例。那也就意味着其實是有關閉sqlsession的過程的。其實,對於每一個service中的sqlsession是不同的,這是通過mybatis-spring中的org.mybatis.spring.mapper.MapperScannerConfigurer創建sqlsession自動注入到service中的。
而一級緩存的設計是每個sqlsession單獨使用一個緩存空間,不同的sqlsession是不能互相訪問數據的。當然,在sqlsession關閉後,其中數據自然被清空。

特此警告!!!!
當MyBatis與spring整合後,如果沒有事務,一級緩存是失效的!一級緩存是失效的!一級緩存是失效的!
原因就是兩者結合後,sqlsession如果發現當前沒有事務,那麼每執行一個mapper方法,sqlsession就被關閉了。如果需要維持一級緩存的可用性,有兩種途徑:

  1. 添加事務
  2. 使用二級緩存

二級緩存

在使用二級緩存之前,先測試之前提到過的關閉sqlsession後會清空緩存的問題,把junit代碼修改一下

@Test
    public void testFindUsers() throws Exception {
        UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
        List<User> users = userMapper.findUsers();
        //關閉sqlsession
        sqlSession.close();

        //通過sqlsessionFactroy創建一個新的sqlsession
        sqlSession = sqlSessionFactory.openSession();
        //獲取mapper對象
        userMapper = sqlSession.getMapper(UserMapper.class);
        users = userMapper.findUsers();
        System.out.println(users);
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

這段代碼在第一次查詢完後關閉sqlsession,然後創建新的sqlsession和mapper來重新執行一次查詢操作,可以預見,執行結果如圖

這裏寫圖片描述

說明關閉了sqlsession後的確把之前的緩存數據清空了,之後再執行同樣的查詢操作也會再訪問一遍數據庫。爲了解決這個問題,需要使用二級緩存

一級緩存的作用域僅限於一個sqlsession,但是二級緩存的作用域是一個namespace。但並不是意味着同一個namespace創建的mapper可以互相讀取緩存內容,這裏的原則是,如果開啓了二級緩存,那麼在關閉sqlsession後,會把該sqlsession一級緩存中的數據添加到namespace的二級緩存中。

接下測試,先需要開啓二級緩存。

1.打開二級緩存總開關
打開總開關,只需要在mybatis總配置文件中加入一行設置

<settings>
   <!--開啓二級緩存-->
    <setting name="cacheEnabled" value="true"/>
</settings>
  • 1
  • 2
  • 3
  • 4

2.打開需要使用二級緩存的mapper的開關

在需要開啓二級緩存的mapper.xml中加入caceh標籤

<cache/>
  • 1

3.POJO序列化

讓需要使用二級緩存的POJO類實現Serializable接口,如

public class User implements Serializable {
  • 1

通過之前三步操作就可以使用二級緩存了,接下來測試。添加一個Junit方法

@Test
    public void testFindUsersCache() throws Exception {
        UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
        List<User> users = userMapper.findUsers();
        //關閉sqlsession
        sqlSession.close();

        //通過sqlsessionFactroy創建一個新的sqlsession
        sqlSession = sqlSessionFactory.openSession();
        //獲取mapper對象
        userMapper = sqlSession.getMapper(UserMapper.class);
        users = userMapper.findUsers();
        System.out.println(users);
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

執行後可以發現,控制檯值輸出了一次查詢過程,也可以證明二級緩存開啓成功。

還有一個問題,之前說了,即使開啓了二級緩存,不同的sqlsession之間的緩存數據也不是想互訪就能互訪的,必須等到sqlsession關閉了以後,纔會把其一級緩存中的數據寫入二級緩存。爲了測試這個,把上述代碼中的

sqlSession.close();
  • 1

註釋,那麼之前的代碼就變成了
這裏寫圖片描述

再執行,發現控制太又輸出了兩次的查詢過程,所以可以印證,只有關閉了sqlsession之後,纔會把其中一級緩存數據寫入二級緩存。

緩存配置

  • 關閉刷新

在默認情況下,當sqlsession執行commit後會刷新緩存,但是也可以強制設置爲不刷新,在不需要刷新的標籤中加入

flushCache="false" 
  • 1

<select id="findUsers" resultType="cn.elinzhou.mybatisTest.pojo.User" flushCache="false">
  • 1

那麼,無論是否執行commit,緩存都不會刷新了。但是這樣會造成髒讀,只有在特殊情況下才使用

  • 自動刷新

有些情況下,需要設置自動刷新緩存,那麼需要配置對應mapper中的cache標籤。

flushInterval="10000"
  • 1

該屬性表示每隔10秒鐘自動刷新一遍緩存

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章