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

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

注:本筆記是根據尚矽谷的MyBatis視訊記錄的

對於任何一個持久層框架,都有快取機制;快取在電腦中有一塊真實的儲存空間(https://baike.baidu.com/item/%E7%BC%93%E5%AD%98/100710?fr=aladdin);

兩個關於mybatis快取額外的連結:

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

 Mybatis整合第三方快取步驟具體可參考:Mybatis整合第三方快取ehcache

快取原理圖:

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

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決定;

 具體可參考:Mybatis整合第三方快取ehcache