文章目录
- 一、MyBatis的一级缓存
- 1、每个SqlSession都有自己的一级缓存
- 2、同一个SqlSession但是查询条件不同
- 3、 同一个SqlSession两次查询期间执行了任何一次增删改操作
- 4、同一个SqlSession自己手动清空一级缓存
- 二、MyBatis的二级缓存
- 1、二级缓存的相关配置
- 三、一级\二级缓存总结
- 四、整合第三方缓存组件Redis
我们知道,在所有的数据管理的中,查询是最多的操作,如果有一些大量的重复的操作(比如用户总是查询同一条数据)。是否可以把上次查到的内容,放到缓存中,而不必去数据库中建立查询数据,造成资源上的浪费,
MyBatis
就为我们实现了缓存功能。
Mybatis
包含一个非常强大的查询缓存特性,它可以非常方便地配置和定制,缓存可以极大的提升查询效率。
MyBatis中默认定义了两级缓存,分别是一级缓存和二级缓存。一级缓存和二级缓存级别不一样,一级缓存默认开启。缓存只对查询功能有效
- 默认情况下,只有一级缓存(SqlSession级别的缓存,也称为本地缓存(
Local Cache
))开启 - 二级缓存需要手动开启和配置,二级缓存是基于
Mapper(namespace)
级别的缓存 - 为了提高扩展性,
MyBatis
定义了缓存接口Cache
。我们可以通过实现Cache接口
来自定义二级缓存。
一、MyBatis的一级缓存
一级缓存是SqlSession
级别的,通过同一个SqlSession
查询的数据会被缓存,下次查询相同的数据,就 会从缓存中直接获取,不会从数据库重新访问。但是,如何界定哪些数据会从缓存中取用呢?
对于缓存失效的四种情况:
使一级缓存失效的四种情况:
- 不同的
SqlSession
对应不同的一级缓存 - 同一个
SqlSession
但是查询条件不同 - 同一个
SqlSession
两次查询期间执行了任何一次增删改操作 - 同一个
SqlSession
两次查询期间手动清空了缓存
y
1、每个SqlSession都有自己的一级缓存
先表明现象最后推出本质,这里我定义的CacheMapper
接口只是简单的根据员工的id查询出员工。这里可以自己定义,如果对如何定义MyBatis不是很熟的可以看博客从0到1搭建MyBatis实例思路剖析,跟着做完就能入门MyBatis
@Test
public void testCache() throws IOException {
SqlSession sqlSession = getSqlSession();
CacheMapper mapper = sqlSession.getMapper(CacheMapper.class);
Emp emp1 = mapper.getEmpById(1);
System.out.println(emp1);
System.out.println("========第二次调用========从缓存中取数据");
Emp emp2 = mapper.getEmpById(1);
System.out.println(emp2);
System.out.println("========即使用的不是同一个Mapper,也同样从缓存中取(同一个sqlsession)========");
CacheMapper emp3 = sqlSession.getMapper(CacheMapper.class);
System.out.println(emp3);
System.out.println("\n========一级缓存的范围在sqlsession中,换一个新的sqlsession就会再次用sql读取数据========");
SqlSession sqlSession1 = getSqlSession();
CacheMapper mapper1 = sqlSession1.getMapper(CacheMapper.class);
Emp emp4 = mapper1.getEmpById(1);
System.out.println(emp4);
}
对结果进行分析
2、同一个SqlSession但是查询条件不同
给定两个不同的查询条件
@Test
public void testCache2() throws IOException {
SqlSession sqlSession = getSqlSession();
CacheMapper mapper = sqlSession.getMapper(CacheMapper.class);
System.out.println("第一次获取数据,查询条件: eid = 1");
Emp emp1 = mapper.getEmpById(1);
System.out.println(emp1);
System.out.println("第二次获取数据,查询条件:eid = 100");
Emp emp2 = mapper.getEmpById(100);
System.out.println(emp2);
}
多提一嘴:对于所有的缓存结构,基本上可以猜到是键值对的形式。而通过这次分析,可以知道,对于键值对形式的缓存结构,key
一定是和查询条件有关的。
3、 同一个SqlSession两次查询期间执行了任何一次增删改操作
对于缓存,我们知道最大的弊端之一可能就是缓存一致性的问题。如何保证用户从缓存查询到的数据一定是和数据库保持一致的,而不是保存老版的数据。其中一个操作就是,如果有增删改
的操作就直接删缓存内容。
如果在MyBatis
中进行了增删改,会将清空当前SqlSession会话
下的LocalCache
,这种比较极端,实际上最好的解决方案貌似是修改或者删除了哪条就删除缓存中的哪条。
@Test
public void testCache3() throws IOException {
SqlSession sqlSession = getSqlSession();
CacheMapper mapper = sqlSession.getMapper(CacheMapper.class);
System.out.println("第一次获取数据,查询条件: eid = 1");
Emp emp1 = mapper.getEmpById(1);
System.out.println(emp1);
Emp emp2 = mapper.getEmpById(1);
System.out.println(emp2);
System.out.println("\n=====进行增删改操作=====");
mapper.updateNameByEid(100, "zhangsan");
System.out.println("\n=====同一个sqlsession,再获取数据=====");
Emp emp3 = mapper.getEmpById(1);
System.out.println(emp3);
}
最后的结果:
4、同一个SqlSession自己手动清空一级缓存
@Test
public void testCache4() throws IOException {
SqlSession sqlSession = getSqlSession();
CacheMapper mapper = sqlSession.getMapper(CacheMapper.class);
System.out.println("第一次获取数据,查询条件: eid = 1");
Emp emp1 = mapper.getEmpById(1);
System.out.println(emp1);
System.out.println("\n=====两次查询期间手动清空缓存=====");
sqlSession.clearCache();
System.out.println("第一次获取数据,查询条件: eid = 1");
Emp emp2 = mapper.getEmpById(1);
System.out.println(emp2);
}
二、MyBatis的二级缓存
对于一级缓存,通过上述分析知道是属于SqlSessionFactory
级别的。而二级缓存是SqlSessionFactory
级别,通过同一个SqlSessionFactory
创建的SqlSession
查询的结果会被缓存;此后若再次执行相同的查询语句,结果就会从缓存中获取。
对于二级缓存MyBatis
不是默认开启的,需要我们手动进行开启。
二级缓存开启的条件
- 在核心配置文件中,设置全局配置属性
cacheEnabled=“true"
,默认为true
,不需要设置 - 在映射文件中设置标签
<cache />
- 二级缓存必须在
SqlSession
关闭或提交之后有效 - 查询的数据所转换的实体类类型必须实现序列化的接口
使二级缓存失效的情况: 两次查询之间执行了任意的增删改,会使一级和二级缓存同时失效。需要注意的是,如果没有提交sqlsession
时,数据会保存在一级缓存中,提交后,会将一级缓存的内容刷到二级缓存中。
二级缓存执行图:
在映射文件中设置标签<cache />
,比如说对于CacheMapper.xml
开启。
最后直接开始测试,在测试之前需要注意的是:在你需要返回的实体类上实现接口implements Serializable
测试代码:
@Test
public void testCache5() throws IOException {
// 二级缓存是基于sqlsessionfactory的
// 加载核心配置文件
InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
// 获取sqlsessionFactory
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(is);
System.out.println("第一个sqlsession的操作");
SqlSession sqlSession1 = sqlSessionFactory.openSession(false);
System.out.println(sqlSession1.getMapper(CacheMapper.class).getEmpById(1));
// 需要关闭/提交会话,才能将一级缓存刷到二级
sqlSession1.commit(); // 提交,将一级缓存刷到二级缓存中
System.out.println("=====二级缓存未打开,没从缓存中获取数据=====");
SqlSession sqlSession2 = sqlSessionFactory.openSession(false);
System.out.println(sqlSession2.getMapper(CacheMapper.class).getEmpById(1));
}
开启二级缓存;
1、二级缓存的相关配置
其实对于xml
中的cache
标签还可以加很多属性
<!-- 声明这个namespace 使用二级缓存-->
<cache type="org.apache.ibatis.cache.impl.PerpetualCache"
size="1024" <!—-最多缓存对象个数,默认1024-->
eviction="LRU" <!—回收策略-->
flushInterval="120000" <!—自动刷新时间ms,未配置时只有调用时刷新-->
readOnly="false"/> <!—默认是false(安全),改为true 可读写时,对象必 须支持序列
化-->
cache属性详解:
三、一级\二级缓存总结
如果是开启了二级缓存,在MyBatis
中总的查询流程如下所示:
- 先查询二级缓存,因为二级缓存中可能会有其他程序已经查出来的数据,可以拿来直接使用。
- 如果二级缓存没有命中,再查询一级缓存
- 如果一级缓存也没有命中,则查询数据库
SqlSession
关闭之后,一级缓存中的数据会写入二级缓存。
有关于二级缓存和一级缓存的架构位置,是先走的是CachingExecutor
查看是否有二级缓存信息,之后在走一级缓存。
四、整合第三方缓存组件Redis
对于第三方组件的使用,前提条件就是开启了二级缓存。这个操作无非就是将原来的二级缓存的数据移植到了第三方组件中。至于为什么,其中一个就是mybatis
自带的二级缓存,这个缓存是单服务器工作,无法实现分布式缓存。
mybatis
提供了一个cache
接口,如果要实现自己的缓存逻辑,实现cache
接口开发即可。mybatis
本身默认实现了一个,但是这个缓存的实现无法实现分布式缓存,所以我们要自己来实现。
首先,需要导入jar
包
<!-- https://mvnrepository.com/artifact/org.mybatis.caches/mybatis-redis -->
<dependency>
<groupId>org.mybatis.caches</groupId>
<artifactId>mybatis-redis</artifactId>
<version>1.0.0-beta2</version>
</dependency>
修改mapper
配置文件中的type
:
<?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.fckey.mybatis.mapper.CacheMapper">
<!-- 开启二级缓存-->
<!-- 声明这个namespace 使用二级缓存-->
<!-- 开启二级缓存, 需要修改为 -->
<cache type="org.mybatis.caches.redis.RedisCache"/>
在resource
文件夹下新建文件redis.properties
(名字必须叫这个,不然无法获取到里面的数据)
redis.host=127.0.0.1
redis.port=6379
redis.pass=
redis.database=0
redis.maxIdle=300
redis.maxWait=3000
redis.testOnBorrow=true
测试代码:
@Test
public void testCache5() throws IOException {
// 二级缓存是基于sqlsessionfactory的
// 加载核心配置文件
InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
// 获取sqlsessionFactory
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(is);
System.out.println("第一个sqlsession的操作");
SqlSession sqlSession1 = sqlSessionFactory.openSession(false);
System.out.println(sqlSession1.getMapper(CacheMapper.class).getEmpById(1));
// 需要关闭/提交会话,才能将一级缓存刷到二级
sqlSession1.commit(); // 提交,将一级缓存刷到二级缓存中
System.out.println("=====二级缓存打开,从缓存中获取数据=====");
SqlSession sqlSession2 = sqlSessionFactory.openSession(false);
System.out.println(sqlSession2.getMapper(CacheMapper.class).getEmpById(1));
}
最后的结果;
最后我们来看看redis中的数据变化。