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