1. 程式人生 > >Mybatis一級、二級快取

Mybatis一級、二級快取

一級快取

首先做一個測試,建立一個mapper配置檔案和mapper介面,我這裡用了最簡單的查詢來演示。

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

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

然後編寫一個單元測試

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); } }

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

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

然後再加一條語句

users = userMapper.findUsers();

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

這裡寫圖片描述

也就是在執行完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);
    }

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

這裡寫圖片描述

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

一級快取的作用域僅限於一個sqlsession,但是二級快取的作用域是一個namespace。但並不是意味著同一個namespace建立的mapper可以互相讀取快取內容,這裡的原則是,如果開啟了二級快取,那麼在關閉sqlsession後,會把該sqlsession一級快取中的資料新增到namespace的二級快取中。

接下測試,先需要開啟二級快取。

1.開啟二級快取總開關
開啟總開關,只需要在mybatis總配置檔案中加入一行設定

<settings>
   <!--開啟二級快取-->
    <setting name="cacheEnabled" value="true"/>
</settings>

2.開啟需要使用二級快取的mapper的開關

在需要開啟二級快取的mapper.xml中加入caceh標籤

<cache/>

3.POJO序列化

讓需要使用二級快取的POJO類實現Serializable介面,如

public class User implements Serializable {

通過之前三步操作就可以使用二級快取了,接下來測試。新增一個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);
    }

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

還有一個問題,之前說了,即使開啟了二級快取,不同的sqlsession之間的快取資料也不是想互訪就能互訪的,必須等到sqlsession關閉了以後,才會把其一級快取中的資料寫入二級快取。為了測試這個,把上述程式碼中的

sqlSession.close();

註釋,那麼之前的程式碼就變成了
這裡寫圖片描述

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

快取配置

  • 關閉重新整理

在預設情況下,當sqlsession執行commit後會重新整理快取,但是也可以強制設定為不重新整理,在不需要重新整理的標籤中加入

flushCache="false" 

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

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

  • 自動重新整理

有些情況下,需要設定自動重新整理快取,那麼需要配置對應mapper中的cache標籤。

flushInterval="10000"

該屬性表示每隔10秒鐘自動重新整理一遍快取