Mybatis的缓存
缓存
1.什么是缓存?
想想我们之前所有的查询最后都要连接数据库,然而连接数据库很耗资源!
然后我们要想办法解决: 我们想能不能一次查询的结果,给他暂存在一个可以直接取到的地方,这个地方一般在内存里!
放在内存的这一些查询的数据就叫缓存
我们再次查询相同数据的时候,直接走缓存,就不用走数据库了
- 存在内存中的临时数据。
- 将用户经常查询的数据放在缓存(内存)中,用户去查询数据就不用从磁盘上(关系型数据库数据文件)查询,从缓存中查询,从而提高查询效率,解决了高并发系统的性能问题。
2.为什么使用缓存?
- 减少和数据库的交互次数,减少系统开销,提高系统效率。
3.什么样的数据能使用缓存?
- 经常查询并且不经常改变的数据。【可以使用缓存】
- 不经常查询且经常改变的数据。【不可以使用缓存】
Mybatis缓存
- Mybatis包含一个非常强大的查询缓存特性,它可以非常方便地定制和配置缓存。缓存可以极大地提升查询效率。
- Mybatis系统中默认定义了两级缓存: 一级缓存和二级缓存
- 默认情况下,只有一级缓存开启。(SqlSession级别地缓存,也称为本地缓存)
- 二级缓存需要手动开启和配置,他是基于namespace级别的缓存。
- 为了提高扩展性,Mybatis定义了缓存接口Cache。我们可以通过实现Cache接口来自定义二级缓存。
一级缓存:
- 一级缓存也叫本地缓存:SqlSession
- 与数据库同一次会话期间查询到的数据会放在本地缓存中。
- 以后如果需要获取相同的数据,直接从缓存中拿,没必要再去查询数据库。
注意:一级缓存也就是从拿到SqlSession开始,到SqlSession关闭之间存在!
测试步骤:
1.开启日志!
2.测试在一个SqlSession中查询两次相同记录
@Test
public void test(){
//一级缓存开始<<=================
SqlSession sqlSession = MybatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
User user = mapper.queryUserById(1);
System.out.println(user);
System.out.println("=========================");
User user2 = mapper.queryUserById(1);
System.out.println(user2);
System.out.println(user == user2);
sqlSession.close();
//一级缓存结束========================>>
}
3.查看日志输出
一级缓存失效的情况:
1.查询不同的用户,一级缓存失效
2.增删改操作,可能会改变原来的数据,所以必定会刷新缓存!
下面这段代码,第一次查询id为1的用户,中间更新了id=2的用户信息,再查一次id为1的用户,结果显示不会走一级缓存,还是会查询数据库!
@Test
public void test(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
User user = mapper.queryUserById(1);
System.out.println(user);
mapper.updateUser(new User(2,"aaa","bbb"));
System.out.println("=========================");
User user2 = mapper.queryUserById(1);
System.out.println(user2);
System.out.println(user == user2);
sqlSession.close();
}
3.查询不同的mapper.xml
这个就不用测试了,不同的mapper肯定不会有一级缓存!
4.手动清理缓存
下面手动清理缓存sqlSession.clearCache(),查询id为1的用户信息走了两次数据库,没有走缓存,这样也会使缓存失效!
@Test
public void test(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
User user = mapper.queryUserById(1);
System.out.println(user);
//手动清理缓存
sqlSession.clearCache();
System.out.println("=========================");
User user2 = mapper.queryUserById(1);
System.out.println(user2);
System.out.println(user == user2);
sqlSession.close();
}
小结:一级缓存默认是开启的,只在一次SqlSession中有效,也就是拿到连接到关闭连接这个区间段!
一级缓存就是一个map集合,往map里面放东西,取的时候从map里面取!
二级缓存:
-
二级缓存也叫全局缓存,一级缓存作用域太低了,所以诞生了二级缓存
-
基于namespace级别的缓存,一个名称空间,对应一个二级缓存
-
工作机制
-
一个会话查询一条数据,这个数据就会被放在当前会话的一级缓存中
-
如果当前会话关闭了,这个会话对应的一级缓存就没了,但是我们想要的是,会话关闭了,一级缓存中的数据被保存到二级缓存中
-
新的会话查询信息,就可以从二级缓存中获取内容
-
不同的mapper查出的数据会放在自己对应的缓存(map)中
-
测试步骤:
1.在mybatis-config.xml核心配置文件里显式地开启全局缓存!(也即是二级缓存),虽然默认Mybatis是开启的,但我们最好写一下!
<!--显式地开启全局缓存-->
<setting name="cacheEnabled" value="true"/>
2.在要使用二级缓存的Mapper中开启
<!--在当前Mapper.xml中使用二级缓存-->
<cache />
也可以自定义一些参数:
<!--在当前Mapper.xml中使用二级缓存-->
<cache eviction="FIFO"
flushInterval="60000"
size="512"
readOnly="true"/>
3.测试
@Test
public void test(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
SqlSession sqlSession2 = MybatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
UserMapper mapper2 = sqlSession2.getMapper(UserMapper.class);
User user = mapper.queryUserById(1);
System.out.println(user);
User user2 = mapper2.queryUserById(1);
System.out.println(user2);
sqlSession.close();
sqlSession2.close();
}
mapper.xml中不加二级缓存的开启
不加测试结果,走了两次数据库:
mapper.xml文件加上二级缓存的配置:
如下:当第一个会话关闭的时候,将数据缓存到了二级缓存中,一个会话死了,致使另一个会话也能从缓存中拿到数据,缓存能在不同的会话中取到,作用域提升了一级,所以才叫二级缓存!(当然前提是在同一个mapper里面的方法)
@Test
public void test(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
SqlSession sqlSession2 = MybatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
User user = mapper.queryUserById(1);
System.out.println(user);
sqlSession.close();
UserMapper mapper2 = sqlSession2.getMapper(UserMapper.class);
User user2 = mapper2.queryUserById(1);
System.out.println(user2);
System.out.println(user == user2);
sqlSession2.close();
}
出现的问题:
如果mapper.xml文件这样配置二级缓存:
<!--在当前Mapper.xml中使用二级缓存-->
<cache/>
测试会报如下这个序列化错误:
因此我们需要为实体类实现序列化接口:
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User implements Serializable {
private int id;
private String name;
private String pwd;
}
注意:最后的为false是因为一级缓存内容存到了二级缓存中,所以内存地址变化对比为false!
小结:
- 只要开启了二级缓存,在同一个Mapper下就有效
- 所有的数据都会先放在一级缓存中,只有当会话提交或者关闭的时候,才会提交到二级缓存中!
缓存原理
Mybatis的缓存原理:先看二级缓存中有没有缓存的数据,在看一级缓存有没有,如果都没有再走查询数据库!
自定义缓存-ehcache:
除了上门一级,二级缓存之外,我们可以自定义缓存实现,但是我们不自己写,我们引用一个第三方写好的自定义缓存ehcache!
Ehcache是一种广泛使用的开源Java分布式缓存。主要面向通用缓存
使用步骤:
要在我们的程序中使用ehcache,先要导包!
<!-- https://mvnrepository.com/artifact/org.mybatis.caches/mybatis-ehcache -->
<dependency>
<groupId>org.mybatis.caches</groupId>
<artifactId>mybatis-ehcache</artifactId>
<version>1.2.1</version>
</dependency>
并且新建一个ehcache.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"
updateCheck="false">
<!--
diskStore:为缓存路径,ehcache分为内存和磁盘两级,此属性定义磁盘的缓存位置。参数解释如下:
user.home - 用户主目录
user.dir - 用户当前工作目录
java.io.tmpdir - 默认临时文件路径
-->
<diskStore path="./tmpdir/Tmp_EhCache"/>
<!--
defaultCache:默认缓存策略,当ehcache找不到定义的缓存时,则使用这个缓存策略。只能定义一个。
-->
<defaultCache
eternal="false"
maxElementsInMemory="10000"
overflowToDisk="false"
diskPersistent="false"
timeToIdleSeconds="1800"
timeToLiveSeconds="259200"
memoryStoreEvictionPolicy="LRU"/>
<!--
name:缓存名称.
maxElementsInMemory:缓存最大数目
maxElementsOnDisk:硬盘最大缓存个数
eternal:对象是否永久有效,一旦设置了,timeout将不起作用。
overflowToDisk:当系统宕机时,是否保存到磁盘
diskPersistent:是否缓存虚拟机重启期数据
timeToIdleSeconds:设置对象在失效前的允许闲置时间
timeToLiveSeconds:设置对象在失效前允许存活时间
memoryStoreEvictionPolicy:可选清除对象策略:LRU(最近最少使用,默认策略)、FIFO(先进先出)、LFU(最少访问次数)。
-->
<cache
name="cloud_user"
eternal="false"
maxElementsInMemory="5000"
overflowToDisk="false"
diskPersistent="false"
timeToIdleSeconds="1800"
timeToLiveSeconds="1800"
memoryStoreEvictionPolicy="LRU"/>
</ehcache>
然后再对应mapper.xml文件中按照如下写:
<!--在当前Mapper.xml中使用ehcache自定义缓存-->
<cache type="org.mybatis.caches.ehcache.EhcacheCache"/>
然后测试效果跟一级、二级缓存没啥区别:
最后提一句: 我们在工作中会用Redis数据库做缓存!
码云地址:https://gitee.com/mo18/Mybatis-Study.git 这篇文章在mybatis-09模块!