MyBatis筆記 | 詳解MyBatis緩存機制

目錄

概述

一級緩存(本地緩存)

一級緩存失效的四種情況

1.當sqlSession不同時

2.當SqlSession相同,但是查詢條件不同時

3.當SqlSession相同,但是多次查詢之間進行了增刪改時

4.SqlSession相同,手動清除了一級緩存(緩存清空)

二級緩存

工作機制

使用步驟

與緩存有關的設置及屬性

緩存原理


概述

Mybatis包含一個非常強大的查詢緩存特性,它可以非常方便地配置和定製。緩存可以極大的提升查詢效率。MyBatis系統中默認定義了兩級緩存:一級緩存和二級緩存

  • 默認情況下,只有一級緩存(SqlSession級別的緩存,也稱爲本地緩存)開啓。
  • 二級緩存需要手動開啓和配置,它是基於namespace級別的緩存,即全局範圍緩存
  • 爲了提高擴展性,MyBatis定義了緩存接口Cache。我們可以通過實現Cache接口來自定義二級緩存。

一級緩存(本地緩存)

是SqlSession級別的緩存,一級緩存是一致開啓的,是一個SqlSession的Map
與數據庫同一次會話期間查詢到的數據會放在本地緩存中,以後如果需要獲取相同的數據,直接從緩存中拿,沒必要再去查詢數據庫。

    @Test
    public void testFirstLevelCache() throws IOException {
        SqlSessionFactory factory = getSqlSessionFactory();
        SqlSession session = factory.openSession();
        try{
            EmployeeMapper mapper = session.getMapper(EmployeeMapper.class);
            Employee emp1 = mapper.getEmpById(1);
            System.out.println(emp1);
            Employee emp2 = mapper.getEmpById(1);
            System.out.println(emp2);
        }finally {
            session.close();
        }
    }

在上述代碼中我們定義了一個emp1和emp2對象,都是查詢的tb1_employee表中的id爲1的記錄,正常的思維來說,我們覺得這樣調用會發送兩條sql語句,而實際上MyBatis只發送了一條sql語句:

 
13424350-2258fec9978b7ea7.png
圖1:控制檯打印的sql語句

 

一級緩存失效的四種情況

1.當sqlSession不同時

因爲一級緩存是SqlSession級別的緩存,那麼當SqlSession不同時,那麼前一個sqlSession的一級緩存對於第二個SqlSession來說肯定是失效的,我們來看個例子:

    @Test
    public void testFirstLevelCache() throws IOException {
        SqlSessionFactory factory = getSqlSessionFactory();
        SqlSession session = factory.openSession();
        SqlSession session1 = factory.openSession();
        try{
            EmployeeMapper mapper = session.getMapper(EmployeeMapper.class);
            EmployeeMapper mapper1 = session1.getMapper(EmployeeMapper.class);
            Employee emp = mapper.getEmpById(1);
            System.out.println(emp);
            Employee emp2 = mapper1.getEmpById(1);
            System.out.println(emp2);
        }finally {
            session.close();
        }
    }

對於這個例子,我們定義了兩個SqlSession,並分別使用它來創建兩個EmployeeMapper的代理對象,然後empemp2是兩個代理對象分別調用方法查詢id=1的字段,我們看圖2可知,其發了兩次sql語句。

 
13424350-932e1ab542ab70a1.png
圖2:控制檯打印的sql語句

 

2.當SqlSession相同,但是查詢條件不同時

    @Test
    public void testFirstLevelCache() throws IOException {
        SqlSessionFactory factory = getSqlSessionFactory();
        SqlSession session = factory.openSession();
        try{
            EmployeeMapper mapper = session.getMapper(EmployeeMapper.class);
            Employee emp1 = mapper.getEmpById(1);
            Employee emp2 = mapper.getEmpById(2);
            System.out.println(emp1);
            System.out.println(emp2);
        }finally {
            session.close();
        }
    }

上述例子中,一個是查詢id=1,另一個是查詢id=2的記錄。因爲第二個查詢與第一個查詢的條件不一樣,所以第二個查詢在本地緩存中沒有對應的數據,因此理所應當會發送sql語句,結果如下圖3所示:

 
13424350-27f70f986bae734e.png
圖3

 

3.當SqlSession相同,但是多次查詢之間進行了增刪改時

    @Test
    public void testFirstLevelCache() throws IOException {
        SqlSessionFactory factory = getSqlSessionFactory();
        SqlSession session = factory.openSession();
        try{
            EmployeeMapper mapper = session.getMapper(EmployeeMapper.class);
            Employee emp1 = mapper.getEmpById(1);
            mapper.addEmp(new Employee(null,"1","1","1",new Department()));
            System.out.println("添加成功");
            Employee emp2 = mapper.getEmpById(1);
            System.out.println(emp1);
            System.out.println(emp2);
        }finally {
            session.close();
        }
    }

上述例子中先查詢了一次id=1,然後插入了一條記錄,然後再查詢一次id=1。這樣的話,兩次查詢都會發送sql語句。因爲可能這次增刪改操作會對當前數據有影響,結果如下圖4所示,紅色方框爲前後兩次查詢的sql語句,綠色爲添加操作的語句:

 
13424350-5a52a481625e12df.png
圖4

 

4.SqlSession相同,手動清除了一級緩存(緩存清空)

    @Test
    public void testFirstLevelCache() throws IOException {
        SqlSessionFactory factory = getSqlSessionFactory();
        SqlSession session = factory.openSession();
        try{
            EmployeeMapper mapper = session.getMapper(EmployeeMapper.class);
            Employee emp1 = mapper.getEmpById(1);
            session.clearCache();   //清除緩存
            Employee emp2 = mapper.getEmpById(1);
            System.out.println(emp1);
            System.out.println(emp2);
        }finally {
            session.close();
        }
    }

上述代碼中,查詢了一次id=1記錄之後,使用了clearCache()清除了sqlSession的緩存,然後再查詢了id=1記錄。此時會發現其也是發送了兩次sql:

 
13424350-c8672ba257649728.png
圖5

 


二級緩存

一個namespace對應一個二級緩存,即是在namespace中全局範圍的。

工作機制

  • 一個會話,查詢一條數據,這個數據就會被放在當前會話的一級緩存中;
  • 如果會話關閉,一級緩存中的數據會被保存到二級緩存中;當有新的會話查詢信息時,就可以參照二級緩存中的數據。
  • 不同namespace查出的數據會放在自己對應的緩存中(map中)
  • 需要注意的是,查出的數據會被默認存放到一級緩存中;只有會話提交或者關閉之後,一級緩存中的數據纔會轉移到二級緩存中。

使用步驟

1.開啓全局二級緩存配置

    <settings>
        <setting name="cacheEnabled" value="true"/>
    </settings>

2.在SQL映射文件中配置使用二級緩存:在<mapper>標籤中配置<cache>子標籤即可

有如下的屬性:

  • eviction:緩存回收策略
  • flushInterval:緩存刷新間隔,指定緩存多長時間(以毫秒爲單位)清空一次,默認不清空
  • readOnly:緩存是否只讀。
    如果爲true,則會只讀。mybatis認爲所有從緩存中獲取數據的操作都是隻讀操作,不會修改數據。mybatis爲了加快獲取速度,直接就會將數據在緩存中的引用交給用戶,不安全,速度快。
    如果爲false,則爲非只讀。mybatis認爲所有從緩存中獲取數據可能會被修改,所以mybatis會利用序列化和反序列化的技術克隆一份新的數據給你,安全,速度慢。
  • size:緩存存放元素的大小。
  • type:指定自定義緩存的全類名。實現Cache接口即可。
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.cerr.mybatis.dao.EmployeeMapperDynamicSQL">
    <!-- 配置使用二級緩存 -->
    <cache eviction="FIFO" flushInterval="60000" readOnly="false" size="1024"></cache>
</mapper>

3.我們的POJO需要實現序列化接口Serializable

public class Employee implements Serializable{

}
public class Department implements Serializable{

}

在測試方法中:

    @Test
    public void testSecondLevelCache() throws IOException{
        SqlSessionFactory factory = getSqlSessionFactory();
        SqlSession session = factory.openSession();
        SqlSession session1 = factory.openSession();
        try{
            EmployeeMapper mapper = session.getMapper(EmployeeMapper.class);
            EmployeeMapper mapper1 = session1.getMapper(EmployeeMapper.class);
            Employee employee1 = mapper.getEmpById(1);
            System.out.println(employee1);
            session.close();
            Employee employee2 = mapper1.getEmpById(1);
            System.out.println(employee2);
            session1.close();
        }finally {}
    }

使用了不同的SqlSession獲取的代理對象查詢id=1的字段(每個SqlSession查詢完就關閉)。則只會發送一條SQL語句。不過要注意的一點是,得先把第一個SqlSession關閉後查使用第二個SqlSession查詢時,二級緩存中才有數據;如果第一個沒關閉,則二級緩存中是沒有數據的

 
13424350-85977bc6b7a067ed.png
圖6

與緩存有關的設置及屬性

全局配置中的cacheEnabled屬性

如果設置爲true,則開啓二級緩存;如果設置爲false,則會關閉二級緩存,但是一級緩存可用。

SQL映射文件<select>中的useCache屬性

如果設置爲true,二級緩存可用;如果設置爲false,也會關閉二級緩存,一級緩存依然使用。

SQL映射文件<insert><update><delete>中的flushCache屬性

默認爲true,即每次增刪改執行完成後都會清除緩存,一級緩存和二級緩存都會被清空。
<select>標籤中也有這個屬性,默認爲false,在此我們不討論。

SqlSession中的clearCache()方法

只會清除當前SqlSession的一級緩存,不會清空二級緩存。

全局配置中的localCacheScope屬性

稱爲本地緩存作用域,當取值爲SESSION時,當前會話的所有數據保存在會話緩存中;當取值爲STATEMENT時,就禁用了一級緩存。


緩存原理

 
13424350-bab15028b541324c.png
圖7:緩存原理示意圖
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章