Mybatis——緩存機制——言簡意賅篇

1:緩存介紹

  • MyBatis 包含一個非常強大的查詢緩存特性,它可以非 常方便地配置和定製。緩存可以極大的提升查詢效率。

  • MyBatis系統中默認定義了兩級緩存。

    • 一級緩存
    • 二級緩存

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

2:一級緩存(本地緩存)

  • 一級緩存(local cache), 即本地緩存, 作用域默認 爲sqlSession。當 Session flush 或 close 後, 該 Session 中的所有 Cache 將被清空。
  • 本地緩存不能被關閉, 但可以調用 clearCache() 來清空本地緩存, 或者改變緩存的作用域.
  • 在mybatis3.1之後, 可以配置本地緩存的作用域. 在 mybatis.xml 中配置

2.1 一級緩存初體驗

    @Test
	public void Test1() throws IOException {
		
		
		SqlSession session=getSqlSessionFactory().openSession();
		EmployeeMapper employeeMapper=session.getMapper(EmployeeMapper.class);
		
		List<Employee> lists=employeeMapper.selectEmpById(1);
		System.out.println(lists);
		
		List<Employee> lists1=employeeMapper.selectEmpById(1);
		System.out.println(lists1);

	}
DEBUG 02-25 22:17:18,950 ==>  Preparing: select * from employee where id=?;   (BaseJdbcLogger.java:145) 
DEBUG 02-25 22:17:19,058 ==> Parameters: 1(Integer)  (BaseJdbcLogger.java:145) 
DEBUG 02-25 22:17:19,117 <==      Total: 1  (BaseJdbcLogger.java:145) 
[Employee{id=1, lastName='zlj', email='[email protected]', gendar='1'department=null}]
[Employee{id=1, lastName='zlj', email='[email protected]', gendar='1'department=null}]

以上結果可以知道,當重複查詢同一條語句的時候,第二次就是去緩存裏面查找,沒去數據庫裏面找了,節省時間

2.2 一級緩存演示失效情況

  • 同一次會話期間只要查詢過的數據都會保存在當 前SqlSession的一個Map中 ,與數據庫同一次會話期間查詢到的數據會放在本地緩存中,以後如果需要獲取相同的數據,直接從緩存中拿,沒必要再去查詢數據庫;
  • 一級緩存失效的四種情況

1、不同的SqlSession對應不同的一級緩存
2、同一個SqlSession但是查詢條件不同
3、同一個SqlSession兩次查詢期間執行了任何一次增 刪改操作
4、同一個SqlSession兩次查詢期間手動清空了緩存

演示:1、不同的SqlSession對應不同的一級緩存

public void Test2() throws IOException {
   
   SqlSession session=getSqlSessionFactory().openSession();
   EmployeeMapper employeeMapper=session.getMapper(EmployeeMapper.class);
   List<Employee> lists=employeeMapper2.selectEmpById(1);
   System.out.println(lists);

   //1、sqlSession不同。(不同的SqlSession對應不同的一級緩存 )
   SqlSession session2=getSqlSessionFactory().openSession();
   EmployeeMapper employeeMapper2=session2.getMapper(EmployeeMapper.class);
   List<Employee> lists2=employeeMapper2.selectEmpById(1);
   System.out.println(lists2);
}
DEBUG 02-25 23:42:18,760 ==>  Preparing: select * from employee where id=?;   (BaseJdbcLogger.java:145) 
DEBUG 02-25 23:42:18,813 ==> Parameters: 1(Integer)  (BaseJdbcLogger.java:145) 
DEBUG 02-25 23:42:18,853 <==      Total: 1  (BaseJdbcLogger.java:145) 
[Employee{id=1, lastName='zlj', email='[email protected]', gendar='1'department=null}]
DEBUG 02-25 23:42:18,962 ==>  Preparing: select * from employee where id=?;   (BaseJdbcLogger.java:145) 
DEBUG 02-25 23:42:18,963 ==> Parameters: 1(Integer)  (BaseJdbcLogger.java:145) 
DEBUG 02-25 23:42:18,966 <==      Total: 1  (BaseJdbcLogger.java:145) 
[Employee{id=1, lastName='zlj', email='[email protected]', gendar='1'department=null}]

從數據庫中查詢了兩次數據,一級緩存失效

演示:2、同一個SqlSession但是查詢條件不同

public void Test2() throws IOException {
   
		SqlSession session=getSqlSessionFactory().openSession();
		EmployeeMapper employeeMapper=session.getMapper(EmployeeMapper.class);
		
		
		//2,sqlSession相同,查詢條件不同.(當前一級緩存中還沒有這個數據)
		//同一個SqlSession但是查詢條件不同 
		List<Employee> lists=employeeMapper.selectEmpById(1);
		System.out.println(lists);
		
		List<Employee> lists1=employeeMapper.selectEmpById(4);
		System.out.println(lists1);
}
DEBUG 02-25 23:43:48,716 ==> Parameters: 1(Integer)  (BaseJdbcLogger.java:145) 
DEBUG 02-25 23:43:48,752 <==      Total: 1  (BaseJdbcLogger.java:145) 
[Employee{id=1, lastName='zlj', email='[email protected]', gendar='1'department=null}]
DEBUG 02-25 23:43:48,756 ==>  Preparing: select * from employee where id=?;   (BaseJdbcLogger.java:145) 
DEBUG 02-25 23:43:48,758 ==> Parameters: 4(Integer)  (BaseJdbcLogger.java:145) 
DEBUG 02-25 23:43:48,760 <==      Total: 1  (BaseJdbcLogger.java:145) 
[Employee{id=4, lastName='jerry4', email='[email protected]', gendar='0'department=null}]

sqlSession相同,查詢條件不同.(當前一級緩存中還沒有這個數據),一級緩存失效,從數據庫中查詢兩次語句

演示:3、同一個SqlSession兩次查詢期間執行了任何一次增 刪改操作

public void Test2() throws IOException {
   
		SqlSession session=getSqlSessionFactory().openSession();
		EmployeeMapper employeeMapper=session.getMapper(EmployeeMapper.class);
		
	  //	3、sqlSession相同,兩次查詢之間執行了增刪改操作(這次增刪改可能對當前數據有影響)
		List<Employee> list=employeeMapper.selectEmpById(1);
		System.out.println(list);
		Employee employee=new Employee(1,"我被修改了","mybatis好牛逼","奧利給");
		boolean num1=employeeMapper.updateEmployeeById(employee);
		System.out.println("是否修改成功"+true);
		List<Employee> lists=employeeMapper.selectEmpById(1);
		System.out.println(lists);
}
DEBUG 02-25 23:46:40,783 ==>  Preparing: select * from employee where id=?;   (BaseJdbcLogger.java:145) 
DEBUG 02-25 23:46:40,834 ==> Parameters: 1(Integer)  (BaseJdbcLogger.java:145) 
DEBUG 02-25 23:46:40,975 <==      Total: 1  (BaseJdbcLogger.java:145) 
[Employee{id=1, lastName='zlj', email='[email protected]', gendar='1'department=null}]
DEBUG 02-25 23:46:40,977 ==>  Preparing: update employee set lastName=?,email=?,gendar=? where id=?;   (BaseJdbcLogger.java:145) 
DEBUG 02-25 23:46:40,979 ==> Parameters: 鎴戣淇敼浜?(String), mybatis濂界墰閫?(String), 濂ュ埄緇?(String), 1(Integer)  (BaseJdbcLogger.java:145) 
DEBUG 02-25 23:46:41,239 <==    Updates: 1  (BaseJdbcLogger.java:145) 
是否修改成功true
DEBUG 02-25 23:46:41,240 ==>  Preparing: select * from employee where id=?;   (BaseJdbcLogger.java:145) 
DEBUG 02-25 23:46:41,241 ==> Parameters: 1(Integer)  (BaseJdbcLogger.java:145) 
DEBUG 02-25 23:46:41,244 <==      Total: 1  (BaseJdbcLogger.java:145) 
[Employee{id=1, lastName='我被修改了', email='mybatis好牛逼', gendar='奧利給'department=null}]

sqlSession相同,兩次查詢之間執行了增刪改操作,導致一級緩存失效

演示:4、同一個SqlSession兩次查詢期間手動清空了緩存

public void Test2() throws IOException {
   
		SqlSession session=getSqlSessionFactory().openSession();
		EmployeeMapper employeeMapper=session.getMapper(EmployeeMapper.class);
		
		//4、sqlSession相同,手動清除了一級緩存(緩存清空)
		List<Employee> lists=employeeMapper.selectEmpById(1);
		System.out.println(lists);
		
		session.clearCache();
		List<Employee> lists1=employeeMapper.selectEmpById(1);
		System.out.println(lists1);
}
DEBUG 02-25 23:49:42,151 ==>  Preparing: select * from employee where id=?;   (BaseJdbcLogger.java:145) 
DEBUG 02-25 23:49:42,196 ==> Parameters: 1(Integer)  (BaseJdbcLogger.java:145) 
DEBUG 02-25 23:49:42,251 <==      Total: 1  (BaseJdbcLogger.java:145) 
[Employee{id=1, lastName='zlj', email='[email protected]', gendar='1'department=null}]
DEBUG 02-25 23:49:42,252 ==>  Preparing: select * from employee where id=?;   (BaseJdbcLogger.java:145) 
DEBUG 02-25 23:49:42,253 ==> Parameters: 1(Integer)  (BaseJdbcLogger.java:145) 
DEBUG 02-25 23:49:42,254 <==      Total: 1  (BaseJdbcLogger.java:145) 
[Employee{id=1, lastName='zlj', email='[email protected]', gendar='1'department=null}]

session.clearCache();語句手動清空了緩存,導致一級緩存失效

3:二級緩存(全局緩存)

  • 二級緩存(second level cache),全局作用域緩存

  • 二級緩存默認不開啓,需要手動配置

  • MyBatis提供二級緩存的接口以及實現,緩存實現要求 POJO實現Serializable接口

  • 二級緩存在 SqlSession 關閉或提交之後纔會生效

  • 工作機制

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

3.1 使用二級緩存的步驟

1)開啓全局二級緩存配置:<setting name="cacheEnabled" value="true"/>
2)去mapper.xml中配置使用二級緩存:
		<cache></cache>
3)我們的POJO需要實現序列化接口

演示二級緩存使用
1:開啓全局二級緩存配置:

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

在mybatis-config.xml文件中配置上,上面配置cacheEnabled

<!--部分代碼截圖-->
 <settings >
    <setting name="cacheEnabled" value="false"></setting>
    <!-- 開啓延遲加載和屬性按需加載 -->
    <setting name="lazyLoadingEnabled" value="true"/>
    <setting name="aggressiveLazyLoading" value="false"/>
    <setting name="cacheEnabled" value="true"/>
 </settings>

2:在EmployMapper.xml文件中配置

<cache eviction="FIFO" flushInterval="60000" readOnly="false" size="1024"></cache> 
對cache中的屬性解讀
 eviction:緩存的回收策略:
		• LRU – 最近最少使用的:移除最長時間不被使用的對象。
		• FIFO – 先進先出:按對象進入緩存的順序來移除它們。
		• SOFT – 軟引用:移除基於垃圾回收器狀態和軟引用規則的對象。
		• WEAK – 弱引用:更積極地移除基於垃圾收集器狀態和弱引用規則的對象。
		• 默認的是 LRU。
	flushInterval:緩存刷新間隔
		緩存多長時間清空一次,默認不清空,設置一個毫秒值
	readOnly:是否只讀:
		true:只讀;mybatis認爲所有從緩存中獲取數據的操作都是隻讀操作,不會修改數據。
				 mybatis爲了加快獲取速度,直接就會將數據在緩存中的引用交給用戶。不安全,速度快
		false:非只讀:mybatis覺得獲取的數據可能會被修改。
				mybatis會利用序列化&反序列的技術克隆一份新的數據給你。安全,速度慢
	size:緩存存放多少元素;
	type="":指定自定義緩存的全類名;
			實現Cache接口即可;

3:POJO需要實現序列化接口
Employee.java

public class Employee implements Serializable{

4:測試

public void Test2() throws IOException {
   
	SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
		SqlSession openSession = sqlSessionFactory.openSession();
		SqlSession openSession2 = sqlSessionFactory.openSession();https://www.huya.com/s
		try{
			//1、
			EmployeeMapper mapper = openSession.getMapper(EmployeeMapper.class);
			EmployeeMapper mapper2 = openSession2.getMapper(EmployeeMapper.class);
			
			List<Employee>emp01 = mapper.selectEmpById(1);
			System.out.println(emp01);
			openSession.close();
			
			//第二次查詢是從二級緩存中拿到的數據,並沒有發送新的sql
			//mapper2.addEmp(new Employee(null, "aaa", "nnn", "0"));
			List<Employee> emp02 = mapper2.selectEmpById(1);
			System.out.println(emp02);
			openSession2.close();
			
		}finally{
			
		}
}
DEBUG 02-26 00:20:06,111 Cache Hit Ratio [com.atstudying.mapper.EmployeeMapper]: 0.0  (LoggingCache.java:62) 
DEBUG 02-26 00:20:06,555 ==>  Preparing: select * from employee where id=?;   (BaseJdbcLogger.java:145) 
DEBUG 02-26 00:20:06,582 ==> Parameters: 1(Integer)  (BaseJdbcLogger.java:145) 
DEBUG 02-26 00:20:06,650 <==      Total: 1  (BaseJdbcLogger.java:145) 
[Employee{id=1, lastName='zlj', email='[email protected]', gendar='1'department=null}]
DEBUG 02-26 00:20:06,661 Cache Hit Ratio [com.atstudying.mapper.EmployeeMapper]: 0.5  (LoggingCache.java:62) 
[Employee{id=1, lastName='zlj', email='[email protected]', gendar='1'department=null}]

根據上面的這一行信息:
DEBUG 02-26 00:20:06,661 Cache Hit Ratio [com.atstudying.mapper.EmployeeMapper]: 0.5 (LoggingCache.java:62)
我們可以知道二級緩存起到了作用,如果我們修改了openSession.close();這一個位置,把它放在最後一行,觀察控制檯的打印語句變化

public void Test2() throws IOException {	
//。。。。。。。		
	//第二次查詢是從二級緩存中拿到的數據,並沒有發送新的sql
	//mapper2.addEmp(new Employee(null, "aaa", "nnn", "0"));
	List<Employee> emp02 = mapper2.selectEmpById(1);
	System.out.println(emp02);
	openSession2.close();
	openSession.close();
	}
DEBUG 02-26 00:28:44,288 Cache Hit Ratio [com.atstudying.mapper.EmployeeMapper]: 0.0  (LoggingCache.java:62) 
DEBUG 02-26 00:28:44,517 ==>  Preparing: select * from employee where id=?;   (BaseJdbcLogger.java:145) 
DEBUG 02-26 00:28:44,552 ==> Parameters: 1(Integer)  (BaseJdbcLogger.java:145) 
DEBUG 02-26 00:28:44,579 <==      Total: 1  (BaseJdbcLogger.java:145) 
[Employee{id=1, lastName='zlj', email='[email protected]', gendar='1'department=null}]
DEBUG 02-26 00:28:44,581 Cache Hit Ratio [com.atstudying.mapper.EmployeeMapper]: 0.0  (LoggingCache.java:62) 
DEBUG 02-26 00:28:44,615 ==>  Preparing: select * from employee where id=?;   (BaseJdbcLogger.java:145) 
DEBUG 02-26 00:28:44,616 ==> Parameters: 1(Integer)  (BaseJdbcLogger.java:145) 
DEBUG 02-26 00:28:44,673 <==      Total: 1  (BaseJdbcLogger.java:145) 
[Employee{id=1, lastName='zlj', email='[email protected]', gendar='1'department=null}]

從二級緩存運行機制中,我們可以知道二級緩存失效的原因是:只有會話提交或者關閉以後,一級緩存中的數據纔會轉移到二級緩存中(第一次查詢完放到一級緩存中,沒有調用openSession.close();語句,代表沒有關閉session,導致一級緩存中的數據沒會轉移到二級緩存中,導致二級緩存失效)

3.2 緩存有關設置

 1、全局setting的cacheEnable:
  – 配置二級緩存的開關。一級緩存一直是打開的。
  
 2、select標籤的useCache屬性:
—配置這個select是否使用二級緩存。一級緩存一直是使用的

 3、sql標籤的flushCache屬性:
   – 增刪改默認flushCache=true。sql執行以後,會同時清空一級和二級緩存。 查詢默認flushCache=false。
     
 4、sqlSession.clearCache():
     – 只是用來清除一級緩存。
     
 5、當在某一個作用域 (一級緩存Session/二級緩存 Namespaces) 進行了 C/U/D 操作後,默認該作用域下所 有 select 中的緩存將被clear。如下圖,這是settings中的屬性

在這裏插入圖片描述
演示:1、全局setting的cacheEnable:
mybatis-conf.xml,cacheEnable=false

 <settings >
   <setting name="cacheEnabled" value="false"></setting>
   <!-- 開啓延遲加載和屬性按需加載 -->
   <setting name="lazyLoadingEnabled" value="true"/>
   <setting name="aggressiveLazyLoading" value="false"/>
   <setting name="cacheEnabled" value="false"/>
</settings>

測試

//演示二級緩存
   @org.junit.jupiter.api.Test
   public void testSecondLevelCache() throws IOException{
   	SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
   	SqlSession openSession = sqlSessionFactory.openSession();
   	SqlSession openSession2 = sqlSessionFactory.openSession();https://www.huya.com/s
   	try{
   		//1、
   		EmployeeMapper mapper = openSession.getMapper(EmployeeMapper.class);
   		EmployeeMapper mapper2 = openSession2.getMapper(EmployeeMapper.class);
   		
   		List<Employee>emp01 = mapper.selectEmpById(1);
   		System.out.println(emp01);
   		openSession.close();

   		//第二次查詢是從二級緩存中拿到的數據,並沒有發送新的sql
   		//mapper2.addEmp(new Employee(null, "aaa", "nnn", "0"));
   		List<Employee> emp02 = mapper2.selectEmpById(1);
   		System.out.println(emp02);
   		openSession2.close();

   		
   	}finally{
   		
   	}
   }

DEBUG 02-26 10:43:23,766 ==>  Preparing: select * from employee where id=?;   (BaseJdbcLogger.java:145) 
DEBUG 02-26 10:43:23,817 ==> Parameters: 1(Integer)  (BaseJdbcLogger.java:145) 
DEBUG 02-26 10:43:23,980 <==      Total: 1  (BaseJdbcLogger.java:145) 
[Employee{id=1, lastName='zlj', email='[email protected]', gendar='1'department=null}]
DEBUG 02-26 10:43:23,982 ==>  Preparing: select * from employee where id=?;   (BaseJdbcLogger.java:145) 
DEBUG 02-26 10:43:23,984 ==> Parameters: 1(Integer)  (BaseJdbcLogger.java:145) 
DEBUG 02-26 10:43:23,991 <==      Total: 1  (BaseJdbcLogger.java:145) 
[Employee{id=1, lastName='zlj', email='[email protected]', gendar='1'department=null}]

因爲配置了 < setting name=“cacheEnabled” value=“false”/>,所以二級緩存關閉了,所以上述得兩次查詢數據庫語句

4:緩存機制原理

在這裏插入圖片描述
緩存機制:
先找二級緩存,再找一級緩存,最後找數據庫

5:第三方緩存整合

  • 除了以上這些方式自定義緩存外,我們還可以通過實現自己的緩存或創建到其他第三方緩存解決方案的適配器來完全覆蓋緩存行爲。
  • EhCache 是一個純Java的進程內緩存框架,具有快速、精 乾等特點,是Hibernate中默認的CacheProvider。
  • MyBatis定義了Cache接口方便我們進行自定義擴展。
  • 使用步驟:

1、導入ehcache包,以及整合包,日誌包 ehcache-core-2.6.8.jar、mybatis-ehcache-1.0.3.jar slf4j-api-1.6.1.jar、slf4j-log4j12-1.6.2.jar
2、編寫ehcache.xml配置文件
3、配置cache標籤 – < cache type= “org.mybatis.caches.ehcache.EhcacheCache” >< /cache >

步驟:1、導入ehcache包,以及整合包,日誌包 ehcache-core-2.6.8.jar、mybatis-ehcache-1.0.3.jar slf4j-api-1.6.1.jar、slf4j-log4j12-1.6.2.jar
2、編寫ehcache.xml配置文件

在這裏插入圖片描述
3,配置cache標籤 – < cache type= “org.mybatis.caches.ehcache.EhcacheCache” >< /cache >

<mapper namespace="com.atstudying.mapper.EmployeeMapper">
	<cache type="org.mybatis.caches.ehcache.EhcacheCache"></cache>

測試二級緩存

//演示二級緩存
	@org.junit.jupiter.api.Test
	public void testSecondLevelCache() throws IOException{
		SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
		SqlSession openSession = sqlSessionFactory.openSession();
		SqlSession openSession2 = sqlSessionFactory.openSession();https://www.huya.com/s
		try{
			//1、
			EmployeeMapper mapper = openSession.getMapper(EmployeeMapper.class);
			EmployeeMapper mapper2 = openSession2.getMapper(EmployeeMapper.class);
			
			List<Employee>emp01 = mapper.selectEmpById(1);
			System.out.println(emp01);
			openSession.close();

			//第二次查詢是從二級緩存中拿到的數據,並沒有發送新的sql
			//mapper2.addEmp(new Employee(null, "aaa", "nnn", "0"));
			List<Employee> emp02 = mapper2.selectEmpById(1);
			System.out.println(emp02);
			openSession2.close();

			
		}finally{
			
		}
	}
DEBUG 02-26 11:12:45,583 ==>  Preparing: select * from employee where id=?;   (BaseJdbcLogger.java:145) 
DEBUG 02-26 11:12:45,610 ==> Parameters: 1(Integer)  (BaseJdbcLogger.java:145) 
DEBUG 02-26 11:12:45,626 <==      Total: 1  (BaseJdbcLogger.java:145) 
[Employee{id=1, lastName='zlj', email='[email protected]', gendar='1'department=null}]
DEBUG 02-26 11:12:45,627 put added 0 on heap  (Segment.java:425) 
DEBUG 02-26 11:12:45,631 Cache Hit Ratio [com.atstudying.mapper.EmployeeMapper]: 0.5  (LoggingCache.java:62) 
[Employee{id=1, lastName='zlj', email='[email protected]', gendar='1'department=null}]
DEBUG 02-26 11:12:45,642 fault removed 0 from heap  (Segment.java:779) 
DEBUG 02-26 11:12:45,642 fault added 0 on disk  (Segment.java:796) 

整合第三方緩存成功

6:小知識

回想前面,對於同一名稱空間中的語句,只會使用或刷新此特定名稱空間的緩存。有時可能需要在名稱空間之間共享相同的緩存配置和實例。在這種情況下,可以使用cache-ref元素引用另一個緩存

<cache-ref namespace="com.someone.application.data.SomeMapper"/>
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章