Mybatis—缓存机制
老规矩,先看官网:https://mybatis.org/mybatis-3/zh/sqlmap-xml.html#cache
一、概念
缓存是什么以及使用缓存的好处大家应该都了解,不了解的话说明你需要努力了,该充电了少年。Mybatis作为司职与数据库打交道的框架,当然会有缓存机制啦,那么在Mybatis中是如何使用缓存的呢?
1、Mybatis中的缓存机制
如果没有缓存,那么每次查询的时候,都要从数据库中检索数据,由于IO的瓶颈,导致整个系统的瓶颈受限于IO的缓慢速度,所以在很多情况下,都会使用到缓存机制。
如果执行SQL语句,会优先从缓存中查询是否能获取到结果。如果获取不到,才会去数据库中查数据,这意味着使用Mybatis查询数据的时候,会把查询到的结果放入到缓存中
2、Mybatis中的缓存分类
- 一级缓存:表示sqlSession级别的缓存,每次查询的时候都会开启一个会话,此会话相当于一个连接,关闭之后自动失效。
- 二级缓存:全局范围内的缓存,sqlSession关闭之后才会生效
- 第三方缓存:继承第三方的组件,来充当缓存的作用
3、怎么区分sql语句是走数据库查询还是走缓存路径?
怎么区分我们的sql语句是走的缓存还是走的数据库查询呢?
如果走缓存的话,是不会执行SQL语句的;而如果没走缓存,必然要从数据库查询,此时就会执行具体的SQL语句。这一点我们可以通过log4j打印执行sql的日志信息验证。
二、一级缓存
1、基本使用
一级缓存表示将查询的数据存储在SqlSession中,一旦关闭SqlSession,缓存就会失效。
一级缓存是默认开启的,在同一个会话之内,如果执行了多条相同的sql语句,那么除了第一条sql语句会从数据库查询,后续的sql都是从缓存中查询的。
2、一级缓存的失效场景
在大部分的情况下一级缓存是可行的,但是有几种特殊的情况会造成一级缓存失效:
1、一级缓存是sqlSession级别的缓存,如果在应用程序中开启了多个sqlsession,不同的SqlSession之间的缓存不共享
一个方法可能会开启多个SqlSession,如果不在同一个SqlSession之内的sql语句,是不能够共享缓存的。
2、在编写查询的sql语句的时候,一定要注意传递的参数,如果参数不一致,那么也不会缓存结果
- 如果传入的参数是基本数据类型,我们很容易就能判断,多条相同的sql语句传入的参数是不一致的,此时并不会使用一级缓存。
- 但是如果语句中传入的参数是引用类型,是一个对象,多条相同的sql语句,传入的参数都是这个对象,此时要注意,如果这个对象里面的属性值是不一样的,那么也不会使用一级缓存。
3、当在一个会话中,在多次查询之间,如果数据库中数据被修改,一级缓存也会失效。
也就是说如果存在并发场景,在另一个SqlSession中修改了数据库中的数据,本SqlSession是不受影响的。
@Test
public void test03(){
SqlSession sqlSession = sqlSessionFactory.openSession();
EmpDao mapper = sqlSession.getMapper(EmpDao.class);
Emp empByEmpno = mapper.findEmpByEmpno(1111);
System.out.println(empByEmpno);
System.out.println("================================");
empByEmpno.setEname("zhangsan");
int i = mapper.updateEmp(empByEmpno);
System.out.println(i);
System.out.println("================================");
Emp empByEmpno1 = mapper.findEmpByEmpno(1111);
System.out.println(empByEmpno1);
sqlSession.close();
}
4、如果在一个会话过程中,手动清空了缓存,那么一级缓存也会失效。
@Test
public void test03(){
SqlSession sqlSession = sqlSessionFactory.openSession();
EmpDao mapper = sqlSession.getMapper(EmpDao.class);
Emp empByEmpno = mapper.findEmpByEmpno(1111);
System.out.println(empByEmpno);
System.out.println("================================");
System.out.println("手动清空缓存");
sqlSession.clearCache();
System.out.println("================================");
Emp empByEmpno1 = mapper.findEmpByEmpno(1111);
System.out.println(empByEmpno1);
sqlSession.close();
}
5、总结
缓存是需要与数据库中的数据保持一致性的,如果多次查询过程中,数据库表中的数据发生了修改,那么缓存就理所当然的要失效了。类比于,在多线程情况下,我们要保证数据的强一致性。
三、二级缓存
默认情况下,只启用了本地的SqlSession缓存,也就是一级缓存,它仅仅对一个会话中的数据进行缓存。
二级缓存表示全局缓存,必须要等到SqlSession关闭后才会才会把当前SqlSession的缓存数据存放到二级缓存。
要启用全局的二级缓存,需要在你的Mapper代理文件中添加一行:
<cache></cache>
当添加上该标签之后,会有如下效果:
- 映射语句文件中的所有 select 语句的结果将会被缓存。
- 映射语句文件中的所有 insert、update 和 delete 语句会刷新缓存。
- 缓存会使用最近最少使用算法(LRU, Least Recently Used)算法来清除不需要的缓存。
- 缓存不会定时进行刷新(也就是说,没有刷新间隔)。
- 缓存会保存列表或对象(无论查询方法返回哪种)的 1024 个引用。
- 缓存会被视为读/写缓存,这意味着获取到的对象并不是共享的,可以安全地被调用者修改,而不干扰其他调用者或线程所做的潜在修改。
1、基本使用
1、在Mybatis的全局配置文件中添加二级缓存的配置
mybatis-config.xml
<settings>
<!--开启驼峰命名规则-->
<setting name="mapUnderscoreToCamelCase" value="true"/>
<!--开启二级(全局)缓存-->
<setting name="cacheEnabled" value="true"/>
</settings>
2、如果要想在某一个Dao接口的所有方法上使用二级缓存,要在其对应的Mapping代理的xml文件中添加配置
比如我想让EmpDao接口定义的方法执行sql语句的时候使用二级缓存,就要在EmpDao接口mapping映射的XML文件中添加如下配置
EmpDao.xml
<!--开启二级缓存-->
<cache></cache>
3、对应的java实体类bean必须要实现序列化的接口
Emp.java
public class Emp implements Serializable {
.......
}
4、进行单元测试
这里我们使用上面一级缓存失效的同一个单元测试test06,原封不动的再次执行。
这次我们可以发现,虽然一级缓存失效了,第三条sql语句和前两条并不在同一个SqlSession,但是仍然是走了缓存,这就是二级缓存的作用了。
2、二级缓存的属性
开启二级缓存:使用标签
<cache></cache>
此标签的属性:
eviction:表示缓存回收策略,默认时LRU(重要)
LRU:最近最少使用,表示移除最长时间不用的对象(重要)
FIFO:先进先出,表示按照对象进入缓存的顺序进行移除(重要)
SOFT:软引用,移除基于垃圾回收器状态和软引用规则的对象
WEAK:弱引用,更积极地移除基于垃圾收集器状态和弱引用规则的对象
flushInternal:缓存刷新间隔,单位毫秒。
默认情况是不设置,也就是没有刷新间隔,缓存仅仅调用语句时刷新。
我们一般情况下也不会设置,了解即可。
size:引用数目,正整数。
代表缓存最多可以存储多少个对象,太大容易导致内存溢出,默认是1024。
我们一般情况下也不会设置,了解即可。
readonly:只读缓存,true/false
true:只读缓存,会给所有调用这返回缓存对象的相同实例,因此这些对象不能被修改。
false:读写缓存,会返回缓存对象的拷贝(序列化实现),这种方式比较安全,默认值就是false。
官网示例:
<cache
eviction="FIFO"
flushInterval="60000"
size="512"
readOnly="true"/>
3、缓存命中率
缓存命中率就是针对二级缓存来说的,一级缓存并没有缓存命中率的概念。
单元测试:总共执行了四次查询操作
@Test
public void test08(){
SqlSession sqlSession1 = sqlSessionFactory.openSession(true);
EmpDao mapper1 = sqlSession1.getMapper(EmpDao.class);
Emp emp1 = mapper1.selectEmpByEmpno(7369);
System.out.println(emp1);
System.out.println("--------------------------------------------");
Emp emp2 = mapper1.selectEmpByEmpno(7369);
System.out.println(emp2);
//关闭会话之后,一级缓存失效
sqlSession1.close();
System.out.println("--------------------------------------------");
//重新开启一个SqlSession
SqlSession sqlSession2 = sqlSessionFactory.openSession(true);
EmpDao mapper2 = sqlSession2.getMapper(EmpDao.class);
Emp emp3 = mapper2.selectEmpByEmpno(7369);
System.out.println(emp3);
System.out.println("--------------------------------------------");
Emp emp4 = mapper2.selectEmpByEmpno(7369);
System.out.println(emp4);
sqlSession2.close();
}
执行结果:
4、缓存查询顺序
当查询数据的时候,应该是先查询一级缓存还是先查询二级缓存?
我们可以通过案例演示,在原来的4条sql语句的基础之上,再新增两条sql:
@Test
public void test08(){
........
System.out.println("--------------------------------------------");
Emp emp5 = mapper2.selectEmpByEmpno(7499);//注意查询的数据是不同的,这次查询的编号是7499
System.out.println(emp5);
System.out.println("--------------------------------------------");
Emp emp6 = mapper2.selectEmpByEmpno(7499);//这次查询是走一级缓存还是二级缓存呢?
System.out.println(emp6);
sqlSession2.close();
}
执行结果分析:
总结:会先尝试从二级缓存中获取数据,获取不到才会走一级缓存,一级缓存也没有就会去数据库查数据。
查询顺序:二级缓存–》 一级缓存 --》数据库sql
四、整合第三方缓存
我们可以实现cache接口来自定义实现缓存,但是几乎没人这么干,一般使用第三方缓存。
通过继承第三方的组件,来充当缓存的作用。
第三方缓存主要使用两种:Redis、ehcache。Mybatis推荐使用ehcache。
以下我们简单演示如何引入和使用ehcache。
1、导入pom依赖
pom.xml
<dependency>
<groupId>org.ehcache</groupId>
<artifactId>ehcache</artifactId>
<version>3.8.1</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.mybatis.caches/mybatis-ehcache -->
<dependency>
<groupId>org.mybatis.caches</groupId>
<artifactId>mybatis-ehcache</artifactId>
<version>1.2.0</version>
</dependency>
2、配置ehcache
eacache.xml
<?xml version="1.0" encoding="UTF-8"?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd">
<!-- 磁盘保存路径 -->
<diskStore path="D:\ehcache" />
<defaultCache
maxElementsInMemory="1"
maxElementsOnDisk="10000000"
eternal="false"
overflowToDisk="true"
timeToIdleSeconds="120"
timeToLiveSeconds="120"
diskExpiryThreadIntervalSeconds="120"
memoryStoreEvictionPolicy="LRU">
</defaultCache>
</ehcache>
3、在Mapper代理文件中使用ehcache缓存
EmpDao.xml
<cache type="org.mybatis.caches.ehcache.EhcacheCache"></cache>
补充:只是简单的介绍了第三方缓存的概念和使用。
一般是用不到的,需要用到第三方缓存的时候再详细学习。
总结
在本篇文章中我们就详细学习了Mybatis中的缓存机制。应该掌握如下知识:
- 掌握Mybatis中缓存概念及分类
- 掌握一级缓存的基本使用和失效场景
- 掌握二级缓存的基本配置使用和配置属性
- 掌握缓存命中率的概念、缓存的查询顺序
- 了解Mybatis对于第三方缓存的支持
再次贴出官网,要学会从官网学习:https://mybatis.org/mybatis-3/zh/sqlmap-xml.html#cache