MyBatis缓存
2023-12-14 16:17:36
MyBatis缓存有一级缓存和二级缓存。
- 一级缓存是SqlSession级别,同一个SqlSession查询的数据会被缓存,下次相同的查询,会直接从缓存读取数据,不会重新查询数据库。
- 二级缓存是SqlSessionFactory级别,通过同一个SqlSessionFactory创建的SqlSession查询的结果会被缓存;此后若再次执行相同的查询语句,结果就会从缓存中获取。
- 默认开启的缓存,即SqlSession级别的缓存
1、SqlSession缓存
@Test
public void testCache01(){
SqlSession sqlSession = SqlSessionUtil.getSqlSession();
CacheMapper cacheMapper = sqlSession.getMapper(CacheMapper.class);
// 同一个session第一次执行查询,走数据库
Emp emp = cacheMapper.selectEmpById(1);
System.out.println(emp);
// 同一个session第二次执行查询,不走数据库,从一级缓存即SqlSession级别的缓存中读取结果
Emp emp2 = cacheMapper.selectEmpById(1);
System.out.println(emp2);
}
Emp selectEmpById(@Param("empId")Integer empId);
<select id="selectEmpById" resultType="Emp">
select * from t_emp where id = #{empId}
</select>
执行后可以看到控制台输出如下:
DEBUG 12-06 22:34:41,564 ==> Preparing: select * from t_emp where id = ? (BaseJdbcLogger.java:135)
DEBUG 12-06 22:34:41,652 ==> Parameters: 1(Integer) (BaseJdbcLogger.java:135)
DEBUG 12-06 22:34:42,174 <== Total: 1 (BaseJdbcLogger.java:135)
Emp{id=1, userName='张三丰', passWord='123', sex='男', deptId=1}
Emp{id=1, userName='张三丰', passWord='123', sex='男', deptId=1}
- 一级缓存失效的情况
- 不同的SqlSession对应不同的一级缓存
- 同一个SqlSession但是查询条件不同
- 同一个SqlSession两次查询期间执行了任何一次增删改操作
- 同一个SqlSession两次查询期间手动清空了缓存
1.1 缓存失效情形一
/**
* 缓存失效情形:
* 同一个sqlSession,中间执行了增删改操作,会使缓存失效
* 同一个sqlSession,不同的查询条件,缓存失效
* 同一个sqlSession,手动清除缓存
*
* DEBUG 12-06 22:44:14,441 ==> Preparing: select * from t_emp where id = ? (BaseJdbcLogger.java:135)
* DEBUG 12-06 22:44:14,607 ==> Parameters: 1(Integer) (BaseJdbcLogger.java:135)
* DEBUG 12-06 22:44:14,735 <== Total: 1 (BaseJdbcLogger.java:135)
* Emp{id=1, userName='张三丰', passWord='123', sex='男', deptId=1}
* DEBUG 12-06 22:44:14,758 ==> Preparing: update t_emp set pass_word = "456" where id = ? (BaseJdbcLogger.java:135)
* DEBUG 12-06 22:44:14,759 ==> Parameters: 2(Integer) (BaseJdbcLogger.java:135)
* DEBUG 12-06 22:44:15,116 <== Updates: 1 (BaseJdbcLogger.java:135)
* 1
* DEBUG 12-06 22:44:15,118 ==> Preparing: select * from t_emp where id = ? (BaseJdbcLogger.java:135)
* DEBUG 12-06 22:44:15,119 ==> Parameters: 1(Integer) (BaseJdbcLogger.java:135)
* DEBUG 12-06 22:44:15,157 <== Total: 1 (BaseJdbcLogger.java:135)
* Emp{id=1, userName='张三丰', passWord='123', sex='男', deptId=1}
*/
@Test
public void testCache02(){
SqlSession sqlSession = SqlSessionUtil.getSqlSession();
CacheMapper cacheMapper = sqlSession.getMapper(CacheMapper.class);
// 同一个session第一次执行查询,走数据库
Emp emp = cacheMapper.selectEmpById(1);
System.out.println(emp);
// 中间进行了一次更新操作,增删改操作均会使得缓存失效
// int i = cacheMapper.updateEmpById(2);
// System.out.println(i);
// 同一个session,不同的查询条件,会使缓存失效
// Emp emp1 = cacheMapper.selectEmpById(2);
// System.out.println(emp1);
// 手动清除缓存
sqlSession.clearCache();;
// 同一个session第二次执行查询,不走数据库,从一级缓存即SqlSession级别的缓存中读取结果
Emp emp2 = cacheMapper.selectEmpById(1);
System.out.println(emp2);
}
1.1 缓存失效情形二
/**
* 缓存失效情形:
* 不同sqlSession,缓存不同
*
* DEBUG 12-06 22:46:28,797 ==> Preparing: select * from t_emp where id = ? (BaseJdbcLogger.java:135)
* DEBUG 12-06 22:46:28,893 ==> Parameters: 1(Integer) (BaseJdbcLogger.java:135)
* DEBUG 12-06 22:46:29,058 <== Total: 1 (BaseJdbcLogger.java:135)
* Emp{id=1, userName='张三丰', passWord='123', sex='男', deptId=1}
* DEBUG 12-06 22:46:29,219 ==> Preparing: select * from t_emp where id = ? (BaseJdbcLogger.java:135)
* DEBUG 12-06 22:46:29,221 ==> Parameters: 1(Integer) (BaseJdbcLogger.java:135)
* DEBUG 12-06 22:46:29,306 <== Total: 1 (BaseJdbcLogger.java:135)
* Emp{id=1, userName='张三丰', passWord='123', sex='男', deptId=1}
*/
@Test
public void testCache03(){
SqlSession sqlSession = SqlSessionUtil.getSqlSession();
CacheMapper cacheMapper = sqlSession.getMapper(CacheMapper.class);
// 不同session第一次执行查询,走数据库
Emp emp = cacheMapper.selectEmpById(1);
System.out.println(emp);
SqlSession sqlSession2 = SqlSessionUtil.getSqlSession();
CacheMapper cacheMapper2 = sqlSession2.getMapper(CacheMapper.class);
// 不同session第二次执行查询,走数据库
Emp emp2 = cacheMapper2.selectEmpById(1);
System.out.println(emp2);
}
2、SqlSessionFactory缓存
开启二级缓存(SqlSessionFactory缓存)的条件:
- 在核心配置文件中,设置全局配置属性cacheEnabled=“true”,默认为true,不需要设置
- 在映射文件中设置标签
- 二级缓存必须在SqlSession关闭或提交之后有效
- 查询的数据所转换的实体类类型必须实现序列化的接口
在mybatis-config.xml中开启缓存
<settings>
<!-- 开启缓存 -->
<setting name="cacheEnabled" value="true"/>
</settings>
实体类实现序列化:
public class Emp implements Serializable {
private Integer id;
private String userName;
private String passWord;
private String sex;
private Integer deptId;
// getter setter
}
在映射文件CacheMapper.xml中配置缓存:
<?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.giser.cache.mapper.CacheMapper">
<!-- 开启二级缓存 -->
<cache />
</mapper>
二级缓存测试:
package com.giser.cache.mapper;
import com.giser.cache.pojo.Emp;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.Test;
import java.io.IOException;
import java.io.InputStream;
/**
* @author giserDev
* @description mybatis二级缓存SessionFactory级别
* @date 2023-12-07 19:23:32
*/
public class SessionFactoryLevelCacheMapperTest {
/**
* 二级缓存是SqlSessionFactory级别,通过同一个SqlSessionFactory创建的SqlSession查询的结果会被
* 缓存;此后若再次执行相同的查询语句,结果就会从缓存中获取
*
* 二级缓存开启的条件:
* a>在核心配置文件mybatis-config.xml中,设置全局配置属性cacheEnabled="true",默认为true,不需要设置
* 开启缓存
* <setting name="cacheEnabled" value="true"/>
*
* b>在映射文件CacheMapper.xml中设置标签
* <cache />
* c>二级缓存必须在SqlSession关闭或提交之后有效
* sqlSession1.commit();
*
* d>查询的数据所转换的实体类类型必须实现序列化的接口
* public class Emp implements Serializable
* 开启二级缓存后,如果对应的实体类没有实现序列化,
* 则会报错:Cause: org.apache.ibatis.cache.CacheException: Error serializing object. Cause: java.io.NotSerializableException: com.giser.cache.pojo.Emp
*
* 使二级缓存失效的情况:
* 两次查询之间执行了任意的增删改,会使一级和二级缓存同时失效
*
*/
@Test
public void testSessionFactoryCache(){
try {
InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(is);
SqlSession sqlSession1 = sqlSessionFactory.openSession();
CacheMapper mapper1 = sqlSession1.getMapper(CacheMapper.class);
Emp emp1 = mapper1.selectEmpById(1);
System.out.println(emp1);
// 二级缓存必须在SqlSession关闭或提交之后有效
sqlSession1.commit();
SqlSession sqlSession2 = sqlSessionFactory.openSession();
CacheMapper mapper2 = sqlSession2.getMapper(CacheMapper.class);
Emp emp2 = mapper2.selectEmpById(1);
System.out.println(emp2);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
执行情况下:
DEBUG 12-07 19:53:57,604 Cache Hit Ratio [com.giser.cache.mapper.CacheMapper]: 0.0 (LoggingCache.java:60)
DEBUG 12-07 19:53:58,077 ==> Preparing: select from t_emp where id = ? (BaseJdbcLogger.java:135)
DEBUG 12-07 19:53:58,153 ==> Parameters: 1(Integer) (BaseJdbcLogger.java:135)
DEBUG 12-07 19:53:58,239 <== Total: 1 (BaseJdbcLogger.java:135)
Emp{id=1, userName='张三丰', passWord='123', sex='男', deptId=1}
WARN 12-07 19:53:58,385 As you are using functionality that deserializes object streams, it is recommended to define the JEP-290 serial filter. Please refer to https://docs.oracle.com/pls/topic/lookup?ctx=javase15&id=GUID-8296D8E8-2B93-4B9A-856E-0A65AF9B8C66 (SerialFilterChecker.java:45)
DEBUG 12-07 19:53:58,397 Cache Hit Ratio [com.giser.cache.mapper.CacheMapper]: 0.5 (LoggingCache.java:60)
Emp{id=1, userName='张三丰', passWord='123', sex='男', deptId=1}
可以看到缓存命中率指标参数(Cache Hit Ratio)由0.0变成了0.5,且第二次并没有查询数据库,说明缓存生效且命中了。
2.1 二级缓存相关配置
在mapper配置文件中添加的cache标签可以设置一些属性:
- eviction属性:缓存回收策略
- LRU(Least Recently Used) – 最近最少使用的:移除最长时间不被使用的对象。
- FIFO(First in First out) – 先进先出:按对象进入缓存的顺序来移除它们。
- SOFT – 软引用:移除基于垃圾回收器状态和软引用规则的对象。
- WEAK – 弱引用:更积极地移除基于垃圾收集器状态和弱引用规则的对象。
默认的是 LRU。
- flushInterval属性:刷新间隔,单位毫秒
默认情况是不设置,也就是没有刷新间隔,缓存仅仅调用语句时刷新 - size属性:引用数目,正整数
代表缓存最多可以存储多少个对象,太大容易导致内存溢出 - readOnly属性:只读,true/false
- true:只读缓存;会给所有调用者返回缓存对象的相同实例。因此这些对象不能被修改。这提供了
很重要的性能优势。 - false:读写缓存;会返回缓存对象的拷贝(通过序列化)。这会慢一些,但是安全,因此默认是
false。
- true:只读缓存;会给所有调用者返回缓存对象的相同实例。因此这些对象不能被修改。这提供了
3、MyBatis缓存查询的顺序
先查询二级缓存,因为二级缓存中可能会有其他程序已经查出来的数据,可以拿来直接使用。
如果二级缓存没有命中,再查询一级缓存
如果一级缓存也没有命中,则查询数据库
SqlSession关闭之后,一级缓存中的数据会写入二级缓存
4、MyBatis整合第三方缓存EHCache
4.1 添加依赖
<!-- Mybatis EHCache整合包 -->
<dependency>
<groupId>org.mybatis.caches</groupId>
<artifactId>mybatis-ehcache</artifactId>
<version>1.2.1</version>
</dependency>
<!-- slf4j日志门面的一个具体实现 -->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.3</version>
</dependency>
4.2 EHCache配置文件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="user.dir"/>
<!--
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,最近最少使用的,缓存的元素有一个时间戳,当缓存容量满了,而又需要腾出地方来缓存新的元素的时候,那么现有缓存元素中时间戳离当前时间最远的元素将被清出缓存。
-->
<defaultCache
maxElementsInMemory="1000"
maxElementsOnDisk="10000000"
eternal="false"
overflowToDisk="true"
timeToIdleSeconds="120"
timeToLiveSeconds="120"
diskExpiryThreadIntervalSeconds="120"
memoryStoreEvictionPolicy="LRU" />
<!--
<cache
name="cloud_user"
eternal="false"
maxElementsInMemory="5000"
overflowToDisk="false"
diskPersistent="false"
timeToIdleSeconds="1800"
timeToLiveSeconds="1800"
memoryStoreEvictionPolicy="LRU"/>
-->
</ehcache>
4.3 在Mapper.xml文件中配置二级缓存类型
<!-- 开启二级缓存 -->
<!-- 设置二级缓存类型 -->
<cache type="org.mybatis.caches.ehcache.EhcacheCache"/>
4.4 加入logback日志配置文件logback.xml
<?xml version="1.0" encoding="UTF-8"?>
<configuration debug="true">
<!-- 指定日志输出的位置 -->
<appender name="STDOUT"
class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<!-- 日志输出的格式 -->
<!-- 按照顺序分别是:时间、日志级别、线程名称、打印日志的类、日志主体内容、换行 -->
<pattern>[%d{HH:mm:ss.SSS}] [%-5level] [%thread] [%logger]
[%msg]%n</pattern>
</encoder>
</appender>
<!-- 设置全局日志级别。日志级别按顺序分别是:DEBUG、INFO、WARN、ERROR -->
<!-- 指定任何一个日志级别都只打印当前级别和后面级别的日志。 -->
<root level="DEBUG">
<!-- 指定打印日志的appender,这里通过“STDOUT”引用了前面配置的appender -->
<appender-ref ref="STDOUT" />
</root>
<!-- 根据特殊需求指定局部日志级别 -->
<logger name="com.giser.cache.mapper" level="DEBUG"/>
</configuration>
```
此时再次运行相关测试代码,可以发现在用户当前工作目录下生成了缓存文件。
文章来源:https://blog.csdn.net/SUNBOYmxbsH/article/details/134912074
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。 如若内容造成侵权/违法违规/事实不符,请联系我的编程经验分享网邮箱:veading@qq.com进行投诉反馈,一经查实,立即删除!
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。 如若内容造成侵权/违法违规/事实不符,请联系我的编程经验分享网邮箱:veading@qq.com进行投诉反馈,一经查实,立即删除!