1. 程式人生 > 實用技巧 >Mybatis的一級快取和二級快取詳解

Mybatis的一級快取和二級快取詳解

快取原理圖:

一、一級快取(本地快取)

sqlSession級別的快取。(相當於一個方法內的快取)

每一次會話都對應自己的一級快取,作用範圍比較小,一旦會話關閉就查詢不到了;

一級快取預設是一直開啟的,是SqlSession級別的一個Map;
與資料庫同一次會話期間查詢到的資料會放在本地快取中。
以後如果需要獲取相同的資料,直接從快取中拿,沒必要再去查詢資料庫;

測試:

取快取中的資料:

說明:當再次查詢發現已經有資料了,就直接在快取中返回之前查的資料,而不再訪問資料庫;

二、一級快取失效的四種情況:

沒有使用到當前一級快取的情況,效果就是:還需要再向資料庫發出查詢

1、sqlsession不同(會話不同)

結果:

說明:不同的會話的快取不共享資料

2、sqlsession相同,查詢快取中沒有的資料

@Test
public void testFirstLevelCache() throws IOException{
    SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
    SqlSession openSession = sqlSessionFactory.openSession();
    try{
        EmployeeMapper mapper = openSession.getMapper(EmployeeMapper.class
); Employee emp01 = mapper.getEmpById(1); System.out.println(emp01); //sqlSession相同,查詢條件不同 Employee emp03 = mapper.getEmpById(3); System.out.println(emp03); System.out.println(emp01==emp03); }finally{ openSession.close(); } }

結果:

說明:當快取中沒有資料時,會重新查資料庫

3、sqlsession相同,但兩次查詢之間執行了增刪改操作

@Test
public void testFirstLevelCache() throws IOException{
    SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
    SqlSession openSession = sqlSessionFactory.openSession();
    try{
        EmployeeMapper mapper = openSession.getMapper(EmployeeMapper.class);
        Employee emp01 = mapper.getEmpById(1);
        System.out.println(emp01);
        
        //sqlSession相同,兩次查詢之間執行了增刪改操作(這次增刪改可能對當前資料有影響)
        mapper.addEmp(new Employee(null, "testCache", "cache", "1"));
        System.out.println("資料新增成功");
        
        Employee emp02 = mapper.getEmpById(1);
        System.out.println(emp02);
        System.out.println(emp01==emp02);
        
    }finally{
        openSession.close();
    }
}

結果:

說明:為了防止增刪改對當前資料的影響,即使查的同一個物件,也會重新查資料庫

原因:每個增刪改標籤都有預設清空快取配置:flushCache="true",不過這是預設的是一級和二級快取都清空

4、sqlsession相同,但手動清楚了一級快取(快取清空)

清空快取:openSession.clearCache();

@Test
public void testFirstLevelCache() throws IOException{
    SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
    SqlSession openSession = sqlSessionFactory.openSession();
    try{
        EmployeeMapper mapper = openSession.getMapper(EmployeeMapper.class);
        Employee emp01 = mapper.getEmpById(1);
        System.out.println(emp01);
        
        //sqlSession相同,手動清除了一級快取(快取清空)
        openSession.clearCache();
        
        Employee emp02 = mapper.getEmpById(1);
        System.out.println(emp02);
        System.out.println(emp01==emp02);
        
    }finally{
        openSession.close();
    }
}

結果:

說明:手動清空快取後,需要重新查資料庫

三、二級快取(全域性快取)

基於namespace名稱空間級別的快取:一個namespace對應一個二級快取

即一個mapper.xml對應一個快取:

1、工作機制:

* 1、一個會話,查詢一條資料,這個資料就會被放在當前會話的一級快取中;
* 2、如果會話關閉;一級快取中的資料會被儲存到二級快取中;新的會話查詢資訊,就可以參照二級快取中的內容;
* 3、sqlSession===EmployeeMapper==>Employee
* DepartmentMapper===>Department
* 不同namespace查出的資料會放在自己對應的快取中(map)
* 效果:資料會從二級快取中獲取
* 查出的資料都會被預設先放在一級快取中。
* 只有會話提交或者關閉以後,一級快取中的資料才會轉移到二級快取中

2、 使用:
* 1)、開啟全域性二級快取配置:<setting name="cacheEnabled" value="true"/>
* 2)、去mapper.xml中配置使用二級快取:
* <cache></cache>
* 3)、我們的POJO需要實現序列化介面

1)在mybatis全域性配置檔案中開啟全域性二級快取配置:<setting name="cacheEnabled" value="true"/>

2)在mapper.xml中配置使用二級快取

直接加上:<cache><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.atguigu.mybatis.dao.EmployeeMapper">
    
     <cache><cache/>
 
     <!--public Map<Integer, Employee> getEmpByLastNameLikeReturnMap(String lastName);  -->
     <select id="getEmpByLastNameLikeReturnMap" resultType="com.atguigu.mybatis.bean.Employee">
         select * from tbl_employee where last_name like #{lastName}
     </select>
</mapper>

或者<cache>中配置一些引數:

eviction:快取的回收策略:
• LRU – 最近最少使用的:移除最長時間不被使用的物件。
• FIFO – 先進先出:按物件進入快取的順序來移除它們。
• SOFT – 軟引用:移除基於垃圾回收器狀態和軟引用規則的物件。
• WEAK – 弱引用:更積極地移除基於垃圾收集器狀態和弱引用規則的物件。
• 預設的是 LRU。
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.atguigu.mybatis.dao.EmployeeMapper">
 
     <cache eviction="FIFO" flushInterval="60000" readOnly="false" size="1024"></cache> 
 
     <!--public Map<Integer, Employee> getEmpByLastNameLikeReturnMap(String lastName);  -->
     <select id="getEmpByLastNameLikeReturnMap" resultType="com.atguigu.mybatis.bean.Employee">
         select * from tbl_employee where last_name like #{lastName}
     </select>
</mapper>

3)POJO需要實現序列化介面

測試:

注意:需要openSession.close();後,才能從二級快取中查資料;

@Test
public void testSecondLevelCache() throws IOException{
    SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
    SqlSession openSession = sqlSessionFactory.openSession();
    SqlSession openSession2 = sqlSessionFactory.openSession();
    try{
 
        EmployeeMapper mapper = openSession.getMapper(EmployeeMapper.class);
        EmployeeMapper mapper2 = openSession2.getMapper(EmployeeMapper.class);
        
        Employee emp01 = mapper.getEmpById(1);
        System.out.println(emp01);
        openSession.close();
        
        //第二次查詢是從二級快取中拿到的資料,並沒有傳送新的sql
        Employee emp02 = mapper2.getEmpById(1);
        System.out.println(emp02);
        openSession2.close();
        
    }finally{
        
    }
}

結果:

說明:第二次查詢是從二級快取中拿到的資料,並沒有傳送新的sql;

注意:

如果openSession.close();在第二次查詢之後才關閉,則第二次查詢會從一級快取中查,如果不是一個session,則查詢不到資料:

@Test
public void testSecondLevelCache() throws IOException{
    SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
    SqlSession openSession = sqlSessionFactory.openSession();
    SqlSession openSession2 = sqlSessionFactory.openSession();
    try{
 
        EmployeeMapper mapper = openSession.getMapper(EmployeeMapper.class);
        EmployeeMapper mapper2 = openSession2.getMapper(EmployeeMapper.class);
        
        Employee emp01 = mapper.getEmpById(1);
        System.out.println(emp01);
        
        Employee emp02 = mapper2.getEmpById(1);
        System.out.println(emp02);
        openSession.close();
        openSession2.close();
        
    }finally{
        
    }
}

結果:

說明:第二次又重新發送了sql,因為從二級快取中取資料時,會話沒關閉所以二級快取中沒資料,所以又去一級快取中查詢,也沒有資料則傳送了sql查資料庫;

所以,只有會話關閉或提交後,一級快取中的資料才會轉移到二級快取中,然後因為是同一個namespace所以可以獲取到資料;

關於Mybatis的一級快取和二級快取執行順序具體可參考:Mybatis的一級快取和二級快取執行順序

四、和快取有關的設定/屬性

1)、mybatis全域性配置檔案中配置全域性快取開啟和清空

1.1)控制二級快取的開啟和關閉

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

cacheEnabled=true:開啟快取;false:關閉快取(二級快取關閉)(一級快取一直可用的)

1.2)控制一級快取的開啟和關閉

<setting name="localCacheScope" value="SESSION"/>

localCacheScope:本地快取作用域(一級快取SESSION);

當前會話的所有資料儲存在會話快取中;STATEMENT:可以禁用一級快取;

注意:一級快取關閉後,二級快取自然也無法使用;

2)、方法中sqlSession清除快取測試

sqlSession.clearCache();只是清除當前session的一級快取;

如果openSession清空了快取,即執行了openSession.clearCache()方法:

結果:

說明:openSession清空快取不影響二級快取;只清空了一級快取;因為在openSession.close()時,就將一級快取儲存至了二級快取;

3)、mapper.xml中也可以配置一級和二級快取開啟和使用

3.1)每個select標籤都預設配置了useCache="true":
如果useCache= false:則表示不使用快取(一級快取依然使用,二級快取不使用)
3.2)每個增刪改標籤預設配置了flushCache="true":(一級二級都會清除)


增刪改執行完成後就會清除快取;

測試:預設flushCache="true":一級快取和二級快取都會被清空;

執行增加操作:

結果:

注意:查詢標籤<select>預設flushCache="false":如果flushCache=true;每次查詢之後都會清空快取;一級和二級快取都無法使用;

五、整合第三方快取

mybatis通過map實現的快取,很不專業;此時可以通過整合第三方快取來達到快取的目的;

做法:實現Cache.java介面中的方法即可:

如實現putObject()方法,往快取中寫資料;實現getObject()方法,從快取中獲取資料;至於什麼時候呼叫,由mybatis決定;