概述
Mybatis包含一個(gè)非常強(qiáng)大的查詢緩存特性奈应,它可以非常方便地配置和定制。緩存可以極大的提升查詢效率。MyBatis系統(tǒng)中默認(rèn)定義了兩級(jí)緩存:一級(jí)緩存和二級(jí)緩存跃脊。
- 默認(rèn)情況下,只有一級(jí)緩存(SqlSession級(jí)別的緩存苛吱,也稱為本地緩存)開啟酪术。
- 二級(jí)緩存需要手動(dòng)開啟和配置,它是基于
namespace
級(jí)別的緩存翠储,即全局范圍緩存拼缝。 - 為了提高擴(kuò)展性,MyBatis定義了緩存接口Cache彰亥。我們可以通過(guò)實(shí)現(xiàn)Cache接口來(lái)自定義二級(jí)緩存咧七。
一級(jí)緩存(本地緩存)
是SqlSession級(jí)別的緩存,一級(jí)緩存是一致開啟的任斋,是一個(gè)SqlSession的Map
與數(shù)據(jù)庫(kù)同一次會(huì)話期間查詢到的數(shù)據(jù)會(huì)放在本地緩存中继阻,以后如果需要獲取相同的數(shù)據(jù),直接從緩存中拿废酷,沒(méi)必要再去查詢數(shù)據(jù)庫(kù)瘟檩。
@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();
}
}
在上述代碼中我們定義了一個(gè)emp1和emp2對(duì)象,都是查詢的tb1_employee
表中的id
為1的記錄澈蟆,正常的思維來(lái)說(shuō)墨辛,我們覺(jué)得這樣調(diào)用會(huì)發(fā)送兩條sql語(yǔ)句,而實(shí)際上MyBatis只發(fā)送了一條sql語(yǔ)句:
一級(jí)緩存失效的四種情況
1.當(dāng)sqlSession不同時(shí)
因?yàn)橐患?jí)緩存是SqlSession級(jí)別的緩存,那么當(dāng)SqlSession不同時(shí)摩骨,那么前一個(gè)sqlSession的一級(jí)緩存對(duì)于第二個(gè)SqlSession來(lái)說(shuō)肯定是失效的,我們來(lái)看個(gè)例子:
@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();
}
}
對(duì)于這個(gè)例子幕屹,我們定義了兩個(gè)SqlSession
太惠,并分別使用它來(lái)創(chuàng)建兩個(gè)EmployeeMapper
的代理對(duì)象磨淌,然后emp
和emp2
是兩個(gè)代理對(duì)象分別調(diào)用方法查詢id=1
的字段,我們看圖2可知凿渊,其發(fā)了兩次sql語(yǔ)句梁只。
2.當(dāng)SqlSession相同,但是查詢條件不同時(shí)
@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();
}
}
上述例子中埃脏,一個(gè)是查詢id=1
搪锣,另一個(gè)是查詢id=2
的記錄。因?yàn)榈诙€(gè)查詢與第一個(gè)查詢的條件不一樣彩掐,所以第二個(gè)查詢?cè)诒镜鼐彺嬷袥](méi)有對(duì)應(yīng)的數(shù)據(jù)构舟,因此理所應(yīng)當(dāng)會(huì)發(fā)送sql語(yǔ)句,結(jié)果如下圖3所示:
3.當(dāng)SqlSession相同佩谷,但是多次查詢之間進(jìn)行了增刪改時(shí)
@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
谐檀。這樣的話抡谐,兩次查詢都會(huì)發(fā)送sql語(yǔ)句。因?yàn)榭赡苓@次增刪改操作會(huì)對(duì)當(dāng)前數(shù)據(jù)有影響桐猬,結(jié)果如下圖4所示麦撵,紅色方框?yàn)榍昂髢纱尾樵兊膕ql語(yǔ)句,綠色為添加操作的語(yǔ)句:
4.SqlSession相同溃肪,手動(dòng)清除了一級(jí)緩存(緩存清空)
@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
記錄羔沙。此時(shí)會(huì)發(fā)現(xiàn)其也是發(fā)送了兩次sql:
二級(jí)緩存
一個(gè)namespace
對(duì)應(yīng)一個(gè)二級(jí)緩存,即是在namespace
中全局范圍的厨钻。
工作機(jī)制
- 一個(gè)會(huì)話扼雏,查詢一條數(shù)據(jù),這個(gè)數(shù)據(jù)就會(huì)被放在當(dāng)前會(huì)話的一級(jí)緩存中夯膀;
- 如果會(huì)話關(guān)閉诗充,一級(jí)緩存中的數(shù)據(jù)會(huì)被保存到二級(jí)緩存中;當(dāng)有新的會(huì)話查詢信息時(shí)诱建,就可以參照二級(jí)緩存中的數(shù)據(jù)蝴蜓。
- 不同
namespace
查出的數(shù)據(jù)會(huì)放在自己對(duì)應(yīng)的緩存中(map中) - 需要注意的是,查出的數(shù)據(jù)會(huì)被默認(rèn)存放到一級(jí)緩存中;只有會(huì)話提交或者關(guān)閉之后茎匠,一級(jí)緩存中的數(shù)據(jù)才會(huì)轉(zhuǎn)移到二級(jí)緩存中格仲。
使用步驟
1.開啟全局二級(jí)緩存配置
<settings>
<setting name="cacheEnabled" value="true"/>
</settings>
2.在SQL映射文件中配置使用二級(jí)緩存:在<mapper>
標(biāo)簽中配置<cache>
子標(biāo)簽即可
有如下的屬性:
-
eviction
:緩存回收策略 -
flushInterval
:緩存刷新間隔,指定緩存多長(zhǎng)時(shí)間(以毫秒為單位)清空一次汽抚,默認(rèn)不清空 -
readOnly
:緩存是否只讀抓狭。
如果為true
伯病,則會(huì)只讀造烁。mybatis認(rèn)為所有從緩存中獲取數(shù)據(jù)的操作都是只讀操作,不會(huì)修改數(shù)據(jù)午笛。mybatis為了加快獲取速度惭蟋,直接就會(huì)將數(shù)據(jù)在緩存中的引用交給用戶,不安全药磺,速度快告组。
如果為false
,則為非只讀癌佩。mybatis認(rèn)為所有從緩存中獲取數(shù)據(jù)可能會(huì)被修改木缝,所以mybatis會(huì)利用序列化和反序列化的技術(shù)克隆一份新的數(shù)據(jù)給你,安全围辙,速度慢我碟。 -
size
:緩存存放元素的大小。 -
type
:指定自定義緩存的全類名姚建。實(shí)現(xiàn)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">
<!-- 配置使用二級(jí)緩存 -->
<cache eviction="FIFO" flushInterval="60000" readOnly="false" size="1024"></cache>
</mapper>
3.我們的POJO需要實(shí)現(xiàn)序列化接口Serializable
public class Employee implements Serializable{
}
public class Department implements Serializable{
}
在測(cè)試方法中:
@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獲取的代理對(duì)象查詢id=1
的字段(每個(gè)SqlSession查詢完就關(guān)閉)。則只會(huì)發(fā)送一條SQL語(yǔ)句掸冤。不過(guò)要注意的一點(diǎn)是厘托,得先把第一個(gè)SqlSession關(guān)閉后查使用第二個(gè)SqlSession查詢時(shí),二級(jí)緩存中才有數(shù)據(jù)稿湿;如果第一個(gè)沒(méi)關(guān)閉铅匹,則二級(jí)緩存中是沒(méi)有數(shù)據(jù)的。
與緩存有關(guān)的設(shè)置及屬性
全局配置中的cacheEnabled
屬性
如果設(shè)置為true
饺藤,則開啟二級(jí)緩存包斑;如果設(shè)置為false
,則會(huì)關(guān)閉二級(jí)緩存策精,但是一級(jí)緩存可用舰始。
SQL映射文件<select>
中的useCache
屬性
如果設(shè)置為true
,二級(jí)緩存可用咽袜;如果設(shè)置為false
丸卷,也會(huì)關(guān)閉二級(jí)緩存,一級(jí)緩存依然使用询刹。
SQL映射文件<insert>
谜嫉、<update>
萎坷、<delete>
中的flushCache
屬性
默認(rèn)為true
,即每次增刪改執(zhí)行完成后都會(huì)清除緩存沐兰,一級(jí)緩存和二級(jí)緩存都會(huì)被清空哆档。
<select>
標(biāo)簽中也有這個(gè)屬性,默認(rèn)為false
住闯,在此我們不討論瓜浸。
SqlSession中的clearCache()
方法
只會(huì)清除當(dāng)前SqlSession的一級(jí)緩存,不會(huì)清空二級(jí)緩存比原。
全局配置中的localCacheScope
屬性
稱為本地緩存作用域插佛,當(dāng)取值為SESSION
時(shí),當(dāng)前會(huì)話的所有數(shù)據(jù)保存在會(huì)話緩存中量窘;當(dāng)取值為STATEMENT
時(shí)雇寇,就禁用了一級(jí)緩存。