一 什么是缓存?
1 什么是缓存
缓存(cache):
- 储存在内存当中的临时数据。
- 将用户经常查询的数据放在缓存(内存)当中,用户去查询数据的时候就不需要再去磁盘(disk)上查询了,就去缓存当中查询,进而提高查询的效率,可以解决 高并发系统性能的问题
2 缓存的好处(为什么要缓存?)
我们查询数据的时候是需要去数据库当中访问的,如果有了 缓存就可以减少和数据库的交互次数,减少系统的开销,进而提高系统的效率。
3 什么样的数据可以使用缓存?
- 缓存是针对于经常查询且不常被改变的数据,
- 我们可以得知不经常查询且常被改变的数据 就不适用与缓存 缓存就是失效。
二 mybatis当中的缓存
- Mybatis包含一个非常强大的查询缓存的特性,它可以非常方便的定制和配置缓存。缓存可以极大的提高查询效率
- 在Mybatis当中定义了两种缓存: 一级缓存和二级缓存
- 默认情况下,是一级缓存(sqlsession级别的缓存,也可以叫她为本地缓存)
- 二级缓存需要手动开启和配置,是基于namespace级别的配置
- 为了提高扩展性,mybatis定义了缓存接口Cache。我们可以通过实现Cache接口来自定义二级缓存。
1 一级缓存
一级缓存也可以被称为本地缓存
- 一级缓存是sqlsession级别的缓存,与数据库同一次会话期间(发出一次请求)的所查询到的数据会放入本地缓存(一级缓存)当中
- 如果需要获取相同的数据,直接在缓存当中获取,就不需要去数据库当中获取。(注意 是需要在sqlsseion 会话期间 如是关闭 一级缓存就没有了)
测试
编写UserMapper接口的方法=====》编写UserMapper.xml文件的sql语句====》编写测试类
------编写UserMapper接口的方法
public interface UserMapper {
User getUserByID(@Param("id") int id);
}
-----编写UserMapper.xml文件的sql语句
<mapper namespace="com.peng.dao.UserMapper">
<cache/>
<select id="getUserByID" parameterType="_int" resultType="User">
select * from user where id=#{id}
</select>
</mapper>
--------编写测试类
@Test
//测试一级缓存
public void testOneLevelCache(){
SqlSession sqlSession = MyBatisUtils.getSqlSession();
UserMapper mapper1 = sqlSession.getMapper(UserMapper.class);
User user1 = mapper1.getUserByID(1);
System.out.println(user1);
UserMapper mapper2 = sqlSession.getMapper(UserMapper.class);
User user2 = mapper2.getUserByID(1);
System.out.println(user2);
sqlSession.close();
}
结果展示:
我们可以从上图看出 :
用户1查询时的sql语句是:select * from user where id=? 访问了数据库 。
用户2查询的时候没有sql语句 没有访问数据库直接从一级缓存当中访问。
1.2一级缓存失效时机?
1. sqlsession会话相同但是查询不同的数据。
2.做insert、 update和delete的时候,刷新缓存
3.查询不同的Mapper.xml文件时
4.手动清理缓存的时候
1.2.1 sqlsession会话相同但是查询不同的数据。
@Test
public void testSqlsessionSameConditionDiffrent(){
SqlSession sqlSession = MyBatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
User user3 = mapper.getUserByID(1);
System.out.println(user3);
User user4 = mapper.getUserByID(2);
System.out.println(user4);
sqlSession.close();
}
结果展示:
我们可以发现: 在相同sqlsession当中不同的查询条件 缓存就会失效
观察结果:发现发送了两条SQL语句!很正常的理解
结论:当前缓存中,不存在这个数据
1.2.2 做insert、 update和delete的时候,刷新缓存
测试类代码:
@Test
//测试在做完了urd是否会刷新缓存 缓存失效了
public void testOneLevelCacheShiXiaoURD(){
SqlSession sqlSession = MyBatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
User user5 = mapper.getUserByID(1);
System.out.println(user5);
HashMap map = new HashMap();
map.put("name","一号副本");
map.put("pwd","123456789");
map.put("id",1);
int i = mapper.UpdateUser(map);
User user6 = mapper.getUserByID(1);
System.out.println(user6);
System.out.println(user5==user6);
sqlSession.close();
}
结果展示:
我们可以从中发现:第一次查询 在更新数据后 第二次查询 就重新访问数据库 也就是说缓存在更新数据后失效了!
观察结果:查询在中间执行了增删改操作后,重新执行了
结论:因为增删改操作可能会对当前数据产生影响
1.2.3 .查询不同的Mapper.xml文件时 sqlsession 不同
测试类代码:
@Test
//3.查询不同的Mapper.xml文件时 sqlsession不同
public void testSqlsessiondiffrent(){
SqlSession sqlSession1 = MyBatisUtils.getSqlSession();
SqlSession sqlSession2 = MyBatisUtils.getSqlSession();
UserMapper mapper1 = sqlSession1.getMapper(UserMapper.class);
User user7 = mapper1.getUserByID(1);
System.out.println(user7);
UserMapper mapper2 = sqlSession2.getMapper(UserMapper.class);
User user8 = mapper2.getUserByID(1);
System.out.println(user8);
sqlSession1.close();
sqlSession2.close();
}
结果展示:
我们可以从中发现: sqlsession不同 缓存也会失效!
观察结果:发现发送了两条SQL语句!
结论:每个sqlSession中的缓存相互独立
1.2.4 .手动清理缓存的时候
测试类代码:
@Test
//测试 缓存失效之手动清理缓存
public void testClearCache(){
SqlSession sqlSession = MyBatisUtils.getSqlSession();
UserMapper mapper1 = sqlSession.getMapper(UserMapper.class);
User user1 = mapper1.getUserByID(1);
System.out.println(user1);
sqlSession.clearCache();//手动清除缓存
UserMapper mapper2 = sqlSession.getMapper(UserMapper.class);
User user2 = mapper2.getUserByID(1);
System.out.println(user2);
sqlSession.close();
}
结果展示:
我们可以从图中发现: 在第一次查询访问数据库后 经过手动清理缓存 第二次查询仍然需要访问数据库 也就是说 缓存失效!
2 二级缓存
二级缓存也叫全局缓存,一级缓存的作用于太低了,所以就有了二级缓存。
二级缓存是基于namespace级别的缓存 ,一个名称空间,对应一个二级缓存
工作机制:
- 一个会话查询一条数据,这个数据就会被放在当前会话的一级缓存的。
- 如果当前会话关闭了,那么这个会话对应的一级缓存也就没有了;但是我们想要的是,会话关闭了,一级缓存中的数据被保存到二级缓存当中;
- 新的会话查询信息, 就可以从二级缓存中获取内容;
- 不同的mapper查询出的数据会放在自己对应的缓存当中
步骤:
=======》》》1 显示开启全局缓存 在mybatis-config核心配置文件当中 setting标签
<settings>
<setting name="cacheEnabled" value="true"/>
</settings>
=======》》》2 在Mapper.xml文件当中开启<cache/>
<mapper namespace="com.peng.dao.StudentMapper">
<cache/>
<select id="getStudentByID" parameterType="_int" resultType="Student">
select * from student where id=#{id}
</select>
<select id="getStudent" parameterType="map" resultType="Student">
select * from student where name=#{name} and tid=#{tid}
</select>
</mapper>
也可以给<cache>设置一些参数
官方示例=====>查看官方文档 <cache eviction="FIFO" flushInterval="60000" size="512" readOnly="true"/> 这个更高级的配置创建了一个 FIFO 缓存,每隔 60 秒刷新, 最多可以存储结果对象或列表的 512 个引用,而且返回的对象被认为是只读的, 因此对它们进行修改可能会在不同线程中的调用者产生冲突。
=======>>>3 测试类:
@Test
public void testTwoLevelCache(){
SqlSession sqlSession1 = MyBatisUtils.getSqlSession();
SqlSession sqlSession2 = MyBatisUtils.getSqlSession();
StudentMapper mapper1 = sqlSession1.getMapper(StudentMapper.class);
StudentMapper mapper2 = sqlSession2.getMapper(StudentMapper.class);
HashMap map = new HashMap();
map.put("name","小张");
map.put("tid",1);
List<Student> students = mapper1.getStudent(map);
for (Student student1 : students) {
System.out.println(student1);
}
sqlSession1.close();
System.out.println("===============================");
List<Student> students2 = mapper2.getStudent(map);
for (Student student2 : students2) {
System.out.println(student2);
}
sqlSession2.close();
}
结果展示:
我们可以从图中发现:
两个不同的sqlsession
sqlsession1关闭后 再次查询相同数据的时候 sqlsession2 是可以从缓存当中去获取的 在一级缓存当中失效的情况下 二级缓存是可以的。
只要是在同一个mapper.xml文件当中
注意事项:可能会报错 序列化 我们需要在对应的实体类里 设置序列化的接口
3 自定义缓存
Ehcache是一种广泛使用的开源Java分布式缓存。主要面向通用缓存
步骤:导包------》xml文件-----》引用xml文件-----》测试
1 导包:
<!-- https://mvnrepository.com/artifact/org.mybatis.caches/mybatis-ehcache --> <dependency> <groupId>org.mybatis.caches</groupId> <artifactId>mybatis-ehcache</artifactId> <version>1.1.0</version> </dependency>
2.encache.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 eternal="false" maxElementsInMemory="10000" overflowToDisk="false" diskPersistent="false" timeToIdleSeconds="1800" timeToLiveSeconds="259200" memoryStoreEvictionPolicy="LRU"/> <cache name="cloud_user" eternal="false" maxElementsInMemory="5000" overflowToDisk="false" diskPersistent="false" timeToIdleSeconds="1800" timeToLiveSeconds="1800" memoryStoreEvictionPolicy="LRU"/> <!-- defaultCache:默认缓存策略,当ehcache找不到定义的缓存时,则使用这个缓存策略。只能定义一个。 --> <!-- name:缓存名称。 maxElementsInMemory:缓存最大数目 maxElementsOnDisk:硬盘最大缓存个数。 eternal:对象是否永久有效,一但设置了,timeout将不起作用。 overflowToDisk:是否保存到磁盘,当系统当机时 timeToIdleSeconds:设置对象在失效前的允许闲置时间(单位:秒)。仅当eternal=false对象不是永久有效时使用,可选属性,默认值是0,也就是可闲置时间无穷大。 timeToLiveSeconds:设置对象在失效前允许存活时间(单位:秒)。最大时间介于创建时间和失效时间之间。仅当eternal=false对象不是永久有效时使用,默认是0.,也就是对象存活时间无穷大。 diskPersistent:是否缓存虚拟机重启期数据 Whether the disk store persists between restarts of the Virtual Machine. The default value is false. diskSpoolBufferSizeMB:这个参数设置DiskStore(磁盘缓存)的缓存区大小。默认是30MB。每个Cache都应该有自己的一个缓冲区。 diskExpiryThreadIntervalSeconds:磁盘失效线程运行时间间隔,默认是120秒。 memoryStoreEvictionPolicy:当达到maxElementsInMemory限制时,Ehcache将会根据指定的策略去清理内存。默认策略是LRU(最近最少使用)。你可以设置为FIFO(先进先出)或是LFU(较少使用)。 clearOnFlush:内存数量最大时是否清除。 memoryStoreEvictionPolicy:可选策略有:LRU(最近最少使用,默认策略)、FIFO(先进先出)、LFU(最少访问次数)。 FIFO,first in first out,这个是大家最熟的,先进先出。 LFU, Less Frequently Used,就是上面例子中使用的策略,直白一点就是讲一直以来最少被使用的。如上面所讲,缓存的元素有一个hit属性,hit值最小的将会被清出缓存。 LRU,Least Recently Used,最近最少使用的,缓存的元素有一个时间戳,当缓存容量满了,而又需要腾出地方来缓存新的元素的时候,那么现有缓存元素中时间戳离当前时间最远的元素将被清出缓存。 --> </ehcache>
3.mapper文件当中引用encache.xml配置文件
<!--在当前Mapper.xml中使用二级缓存--> <cache type="org.mybatis.caches.ehcache.EhcacheCache"/>
4.测试类测试
pojo类
@Data public class Blog { private String id; private String title; private String author; private Date createTime; private int views; }
BlogMapper接口
public interface BlogMapper { List<Student> getBlog(); }
blogMapper.xml文件
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "https://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.peng.dao.BlogMapper"> <cache type="org.mybatis.caches.ehcache.EhcacheCache"/> <select id="getBlog" resultType="Blog" > select * from blog </select> </mapper>
测试类
public class testEhcache { public static void main(String[] args) { SqlSession sqlSession = MyBatisUtils.getSqlSession(); BlogMapper mapper = sqlSession.getMapper(BlogMapper.class); List<Student> blogs = mapper.getBlog(); for (Student blog : blogs) { System.out.println(blog); } sqlSession.close(); } }