十二、Mybatis缓存
1️⃣Mybatis缓存简介
为什么要使用缓存? 每次查询都要连接数据库,比较耗资源,我们把查询到的数据暂存到内存里面,下次查询的时候,从内存读取, 这个地方就叫缓存。
什么样的数据适用于缓存? 经常查询且不经常改变的数据
Mybatis系统默认定义了两级缓存:
默认情况下,只有一级缓存开启(SqlSession缓存,也称为本地缓存)
二级缓存需要手动配置,它是基于namespace级别的缓存
Mybatis定义了缓存接口Cache,可以通过实现Cache接口来自定义二级缓存
2️⃣ 一级缓存
🍀一级缓存是sqlsession级别的缓存
- 在操作数据库时,需要构造sqlsession对象,在对象中有一个数据结构(HashMap)用于存储缓存数据
- 不同的sqlsession之间的缓存区域是互相不影响的。
🍀一级缓存工作原理
一级缓存工作原理图解:
第一次发起查询sql查询用户id为1的用户,先去找缓存中是否有id为1的用户,如果没有,再去数据库查询用户信息。得到用户信息,将用户信息存储到一级缓存中
如果sqlsession执行了commit操作(插入,更新,删除),会清空sqlsession中的一级缓存,避免脏读
第二次发起查询id为1的用户,缓存中如果找到了,直接从缓存中获取用户信息
MyBatis默认支持并开启一级缓存
🍀一级缓存测试步骤
(1)开启日志
(2)测试在一个Session中查询两次
(3)查看日志输出
🍀一级缓存演示
Mapper接口:
User getUserById(int id);
xxxMapper.xml:
<select id="getUserById" parameterType="int" resultType="User"> select * from mybatis.user where id=#{id} </select>
Test.java:
@Test public void getUserById(){ SqlSession sqlSession = MybatisUtils.getSqlSession(); UserMapper mapper = sqlSession.getMapper(UserMapper.class); User u=mapper.getUserById(1); System.out.println(u); System.out.println("============="); User user=mapper.getUserById(1); System.out.println(user); System.out.println(u==user); sqlSession.close(); }
🍀缓存失效的情况
当sqlSession不同
当sqlSession对象相同的时候,查询的条件不同,原因是第一次查询时候,一级缓存中没有第二次查询所需要的数据
当sqlSession对象相同,两次查询之间进行了插入的操作
当sqlSession对象相同,手动清除了一级缓存中的数据
🍀一级缓存生命周期
MyBatis在开启一个数据库会话时,会创建一个新的SqlSession对象,SqlSession对象中会有一个新的Executor对象,Executor对象中持有一个新的PerpetualCache对象;当会话结束时,SqlSession对象及其内部的Executor对象还有PerpetualCache对象也一并释放掉。
如果SqlSession调用了close()方法,会释放掉一级缓存PerpetualCache对象,一级缓存将不可用。
如果SqlSession调用了clearCache(),会清空PerpetualCache对象中的数据,但是该对象仍可使用。
SqlSession中执行了任何一个update操作(update()、delete()、insert()),都会清空PerpetualCache对象的数据,但是该对象可以继续使用。
3️⃣二级缓存
🍀二级缓存是mapper级别的缓存
多个sqlsession去操作同一个mapper的sql语句,多个sqlsession可以共用二级缓存,所得到的数据会存在二级缓存区域
二级缓存是跨sqlsession的
二级缓存相比一级缓存的范围更大(按namespace来划分),多个sqlsession可以共享一个二级缓存
🍀二级缓存实现原理
首先要手动开启MyBatis二级缓存
在config.xml设置二级缓存开关
<settings> <!--开启二级缓存--> <setting name="cacheEnabled" value="true"/> </settings> <!-- 需要将映射的javabean类实现序列化 -->
还要在具体的mapper.xml开启二级缓存
<!--开启本Mapper的namespace下的二级缓存--> <cache eviction="LRU" flushInterval="100000"/>
🍀禁用二级缓存
在statement中可以设置useCache=false,禁用当前select语句的二级缓存,默认情况为true
<select id="getStudentById" parameterType="java.lang.Integer" resultType="Student" useCache="false">
在实际开发中,针对每次查询都需要最新的数据sql,要设置为useCache=“false” ,禁用二级缓存。
🍀flushCache标签:刷新缓存(清空缓存)
<select id="getStudentById" parameterType="java.lang.Integer" resultType="Student" flushCache="true">
一般下执行完commit操作都需要刷新缓存,flushCache="true 表示刷新缓存,可以避免脏读。
🍀二级缓存应用场景
对于访问多的查询请求并且用户对查询结果实时性要求不高的情况下,可采用MyBatis二级缓存,降低数据库访问量,提高访问速度,如电话账单查询。
根据需求设置相应的flushInterval:刷新间隔时间,比如三十分钟,24小时等。
🍀二级缓存局限性
MyBatis二级缓存对细粒度的数据级别的缓存实现不好,比如如下需求:对商品信息进行缓存,由于商品信息查询访问量大,但是要求用户每次都能查询最新的商品信息,此时如果使用MyBatis的二级缓存就无法实现当一个商品变化时只刷新该商品的缓存信息而不刷新其它商品的信息,因为MyBatis的二级缓存区域以mapper为单位划分,当一个商品信息变化会将所有商品信息的缓存数据全部清空。解决此类问题需要在业务层根据需求对数据有针对性缓存。
🍀使用二级缓存
(1)开启全局缓存
<!-- 虽然默认开启,但是写上可以让看代码的人明白 --> <setting name="cacheEnabled" value="true"/>
(2)在要使用二级缓存的Mapper.xml中,写标签
<cache eviction="FIFO" flushInterval="60000" size="512" readOnly="true"/>
(3)测试
@Test public void getUserById2(){ SqlSession sqlSession = MybatisUtils.getSqlSession(); SqlSession sqlSession2 = MybatisUtils.getSqlSession(); UserMapper mapper = sqlSession.getMapper(UserMapper.class); UserMapper mapper2 = sqlSession2.getMapper(UserMapper.class); User u=mapper.getUserById(1); System.out.println(u); sqlSession.close(); System.out.println("============"); User user = mapper2.getUserById(1); System.out.println(user==u); sqlSession2.close(); }
(2)问题
我们需要实体类序列化,否则会抛出异常
(4)总结
二级缓存在同一个Mapper下有效
所有的数据都会先放在一级缓存中
当会话提交或者关闭,数据会被转存到二级缓存中
4️⃣ 缓存原理
5️⃣自定义缓存EhCache
🍀EhCache简介
EhCache 是一个纯Java的进程内缓存框架,具有快速、精干等特点,是Hibernate中默认CacheProvider。Ehcache是一种广泛使用的开源Java分布式缓存。主要面向通用缓存,Java EE和轻量级容器。
🍀EhCache使用
(1)导包
<!-- https://mvnrepository.com/artifact/org.mybatis.caches/mybatis-ehcache --> <dependency> <groupId>org.mybatis.caches</groupId> <artifactId>mybatis-ehcache</artifactId> <version>1.2.2</version> </dependency>
(2)写入配置文件(resources->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"> <!-- 磁盘缓存位置 --> <diskStore path="java.io.tmpdir/ehcache"/> <!-- 默认缓存 --> <defaultCache maxEntriesLocalHeap="10000" eternal="false" timeToIdleSeconds="120" timeToLiveSeconds="120" maxEntriesLocalDisk="10000000" diskExpiryThreadIntervalSeconds="120" memoryStoreEvictionPolicy="LRU"> <persistence strategy="localTempSwap"/> </defaultCache> <!-- helloworld缓存 --> <cache name="HelloWorldCache" maxElementsInMemory="1000" eternal="false" timeToIdleSeconds="5" timeToLiveSeconds="5" overflowToDisk="false" memoryStoreEvictionPolicy="LRU"/> </ehcache>
(3)在Mapper中指定
<cache type="org.mybatis.caches.ehcache.EhcacheCache"/>
(4)测试
@Test public void getUserById2(){ SqlSession sqlSession = MybatisUtils.getSqlSession(); SqlSession sqlSession2 = MybatisUtils.getSqlSession(); UserMapper mapper = sqlSession.getMapper(UserMapper.class); UserMapper mapper2 = sqlSession2.getMapper(UserMapper.class); User u=mapper.getUserById(1); System.out.println(u); sqlSession.close(); System.out.println("============"); User user = mapper2.getUserById(1); System.out.println(user==u); sqlSession2.close(); }
🍀自定义缓存
只要实现了org.apache.ibatis.cache.Cache接口,就能定义自己的缓存,但是实现比较复杂,只需要会使用就行,ehcache是继承了AbstractEhcacheCache,该类已经实现了Cache接口。
public class MyCache implements Cache { @Override public String getId() { return null; } @Override public void putObject(Object key, Object value) { } @Override public Object getObject(Object key) { return null; } @Override public Object removeObject(Object key) { return null; } @Override public void clear() { } @Override public int getSize() { return 0; } }
🍀实际开发中使用的缓存
在实际开发中,我们更多的使用Redis来做缓存。