目錄
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語句:
一級緩存失效的四種情況
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
的代理對象,然後emp
和emp2
是兩個代理對象分別調用方法查詢id=1
的字段,我們看圖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所示:
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語句,綠色爲添加操作的語句:
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:
二級緩存
一個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查詢時,二級緩存中才有數據;如果第一個沒關閉,則二級緩存中是沒有數據的。
與緩存有關的設置及屬性
全局配置中的cacheEnabled
屬性
如果設置爲true
,則開啓二級緩存;如果設置爲false
,則會關閉二級緩存,但是一級緩存可用。
SQL映射文件<select>
中的useCache
屬性
如果設置爲true
,二級緩存可用;如果設置爲false
,也會關閉二級緩存,一級緩存依然使用。
SQL映射文件<insert>
、<update>
、<delete>
中的flushCache
屬性
默認爲true
,即每次增刪改執行完成後都會清除緩存,一級緩存和二級緩存都會被清空。<select>
標籤中也有這個屬性,默認爲false
,在此我們不討論。
SqlSession中的clearCache()
方法
只會清除當前SqlSession的一級緩存,不會清空二級緩存。
全局配置中的localCacheScope
屬性
稱爲本地緩存作用域,當取值爲SESSION
時,當前會話的所有數據保存在會話緩存中;當取值爲STATEMENT
時,就禁用了一級緩存。