MyBatis缓存教程
MyBatis 提供了一级缓存和二级缓存的支持,用于提高数据库查询的性能,减少不必要的数据库访问。
一级缓存(SqlSession 级别的缓存)
一级缓存是 MyBatis 中最细粒度的缓存,也称为本地缓存。它存在于每个 SqlSession 的生命周期中,当 SqlSession 被关闭或清空时,该缓存就会被清空。
什么是SqlSession?
SqlSession是Java程序和数据库之间的会话。
作用范围:同一个 SqlSession 中。
举例:
@Test public void test1() { //获取SqlSession SqlSession sqlSession = SqlSessionUtil.getSqlSession(); CacheMapper mapper = sqlSession.getMapper(CacheMapper.class); //首次获取SqlSession中的数据 List<Emp> emps1 = mapper.getEmpByDeptId(2); //第二次获取SqlSession中的数据 List<Emp> emps2 = mapper.getEmpByDeptId(2); emps1.forEach(System.out::println); emps2.forEach(System.out::println); sqlSession.close(); }
输出结果:
结论:
在同一个SqlSession中,可以看出SQL执行了一次,第二次获取数据是从缓存中获取。
一级缓存失效的四种情况
- 不同的 SqlSession 之间,缓存数据互不共享。
举例:
@Test public void test1() { //获取SqlSession SqlSession sqlSession1 = SqlSessionUtil.getSqlSession(); CacheMapper mapper1 = sqlSession1.getMapper(CacheMapper.class); List<Emp> emps1 = mapper1.getEmpByDeptId(2); emps1.forEach(System.out::println); //获取SqlSession SqlSession sqlSession2 = SqlSessionUtil.getSqlSession(); CacheMapper mapper2 = sqlSession2.getMapper(CacheMapper.class); List<Emp> emps2 = mapper2.getEmpByDeptId(2); emps2.forEach(System.out::println); //关闭SqlSession sqlSession1.close(); sqlSession2.close(); }
输出结果:
结论:
从结果可以看出,不同的SqlSession对应不同的一级缓存数据。
- 同一个 SqlSession 中,执行了任何一次增删改操作(即涉及到了数据库写操作)
举例:
@Test public void test1() { //获取SqlSession SqlSession sqlSession1 = SqlSessionUtil.getSqlSession(); CacheMapper mapper1 = sqlSession1.getMapper(CacheMapper.class); //第一次获取SqlSession中的数据 List<Emp> emps1 = mapper1.getEmpByDeptId(2); emps1.forEach(System.out::println); //执行添加操作 int result = mapper1.addEmp(new Emp(0, "evan", 20, "女", null)); System.out.println("添加结果:" + result); //第二次获取SqlSession中的数据 List<Emp> emps2 = mapper1.getEmpByDeptId(2); emps2.forEach(System.out::println); //关闭SqlSession sqlSession1.close(); }
输出结果:
结论:
从结果可以看出,在同一个SqlSession两次执行查询操作期间执行了一次添加操作,导致缓存失效,第二次执行查询操作时,重新获取SqlSession创建缓存。
- 同一个SqlSession两次查询期间手动清空了缓存
举例:
@Test public void test1() { //获取SqlSession SqlSession sqlSession1 = SqlSessionUtil.getSqlSession(); CacheMapper mapper1 = sqlSession1.getMapper(CacheMapper.class); //第一次获取SqlSession中的数据 List<Emp> emps1 = mapper1.getEmpByDeptId(2); emps1.forEach(System.out::println); //清空缓存 sqlSession1.clearCache(); //第二次获取SqlSession中的数据 List<Emp> emps2 = mapper1.getEmpByDeptId(2); emps2.forEach(System.out::println); //关闭SqlSession sqlSession1.close(); }
输出结果:
结论:
同一个SqlSession两次查询期间手动清空了缓存,导致缓存失效。
- 同一个SqlSession但是查询条件不同
举例:
@Test public void test1() { //获取SqlSession SqlSession sqlSession1 = SqlSessionUtil.getSqlSession(); CacheMapper mapper1 = sqlSession1.getMapper(CacheMapper.class); //第一次获取SqlSession中的数据 List<Emp> emps1 = mapper1.getEmpByDeptId(2); emps1.forEach(System.out::println); //第二次获取SqlSession中的数据 List<Emp> emps2 = mapper1.getEmpByDeptId(3); emps2.forEach(System.out::println); //关闭SqlSession sqlSession1.close(); }
输出结果:
**结论:
从结果可以看出,同一个SqlSession查询不同数据,导致缓存失效。
二级缓存(SqlSessionFactory 级别的缓存)
二级缓存是跨 SqlSession 的,其生命周期与 Mapper 映射文件的命名空间相关。它可以被多个 SqlSession 共享,通常用于缓存查询结果,以减少对数据库的访问。
通过同一个SqlSessionFactory创建的SqlSession查询的结果会被缓存;此后若再次执行相同的查询语句,结果就会从缓存中。
获取二级缓存开启的条件:
- 在核心配置文件中,设置全局配置属性
cacheEnabled="true"
,默认为true,不需要设置
<!-- 全局配置 --> <settings> <!-- 默认开启的,可以显式设置 --> <setting name="cacheEnabled" value="true"/> </settings>
- 在映射文件中设置标签
<cache/>
<?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.evan.mybatis.mapper.CacheMapper"> <!-- 设置二级缓存 --> <cache/> </mapper>
- 二级缓存必须在SqlSession关闭或提交之后有效
private static final Log logger = LogFactory.getLog(CacheTest.class); @Test public void test2() { try { //读取MyBatis核心配置文件中的信息 InputStream is = Resources.getResourceAsStream("mybatis-config.xml"); //构建SqlSessionFactory对象 SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(is); //使用SqlSessionFactory创建SqlSession对象 SqlSession sqlSession1 = sqlSessionFactory.openSession(true); CacheMapper mapper1 = sqlSession1.getMapper(CacheMapper.class); System.out.println(mapper1.getEmpByDeptId(1)); sqlSession1.close(); SqlSession sqlSession2 = sqlSessionFactory.openSession(true); CacheMapper mapper2 = sqlSession2.getMapper(CacheMapper.class); System.out.println(mapper2.getEmpByDeptId(1)); sqlSession2.close(); } catch (IOException e) { logger.error(e); } }
输出结果:
从结果可以看出,首次获取完SqlSession中的数据,关闭SqlSession;当第二次获取时,出现异常:
Error serializing object. Cause: java.io.NotSerializableException: com.evan.mybatis.entity.Emp
,这个异常是说查询数据的实体类没有实现序列化。
- 查询的数据所转换的实体类类型必须实现序列化的接口
public class Emp implements Serializable { //声明唯一序列号 private static final long serialVersionUID = -4262890992L; }
最终测试
public class CacheTest { private static final Log logger = LogFactory.getLog(CacheTest.class); @Test public void test2() { try { //读取MyBatis核心配置文件中的信息 InputStream is = Resources.getResourceAsStream("mybatis-config.xml"); //构建SqlSessionFactory对象 SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(is); //通过SqlSessionFactory创建SqlSession对象 SqlSession sqlSession1 = sqlSessionFactory.openSession(true); SqlSession sqlSession2 = sqlSessionFactory.openSession(true); CacheMapper mapper1 = sqlSession1.getMapper(CacheMapper.class); List<Emp> emps1 = mapper1.getEmpByDeptId(1); emps1.forEach(System.out::println); //关闭sqlSession的时候才会把保存在一级缓存中的数据保存到二级缓存中 sqlSession1.close(); CacheMapper mapper2 = sqlSession2.getMapper(CacheMapper.class); List<Emp> emps2 = mapper2.getEmpByDeptId(1); emps2.forEach(System.out::println); //关闭sqlSession的时候才会把保存在一级缓存中的数据保存到二级缓存中 sqlSession2.close(); } catch (IOException e) { logger.error(e); } } }
输出结果:
首次执行查询操作获取数据,第二次从二级缓存中获取的数据。
二级缓存失效的情况
同一个SqlSessionFactory下的SqlSession两次查询相同SQL之间执行了任意的增删改操作,会使一级和二级缓存同时失效。
public class CacheTest { private static final Log logger = LogFactory.getLog(CacheTest.class); @Test public void test2() { try { //读取MyBatis核心配置文件中的信息 InputStream is = Resources.getResourceAsStream("mybatis-config.xml"); //通过SqlSessionFactory创建SqlSession对象 SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(is); SqlSession sqlSession1 = sqlSessionFactory.openSession(true); SqlSession sqlSession2 = sqlSessionFactory.openSession(true); CacheMapper mapper1 = sqlSession1.getMapper(CacheMapper.class); List<Emp> emps1 = mapper1.getEmpByDeptId(1); emps1.forEach(System.out::println); //执行删除操作,缓存失效 System.out.println(mapper1.deleteEmp(13)); //关闭sqlSession的时候才会把保存在一级缓存中的数据保存到二级缓存中 sqlSession1.close(); CacheMapper mapper2 = sqlSession2.getMapper(CacheMapper.class); List<Emp> emps2 = mapper2.getEmpByDeptId(1); emps2.forEach(System.out::println); //关闭sqlSession的时候才会把保存在一级缓存中的数据保存到二级缓存中 sqlSession2.close(); } catch (IOException e) { logger.error(e); } } }
输出结果:
二级缓存的相关配置
在mapper配置文件中添加的cache标签可以设置一些属性:
eviction
属性:缓存回收策略
- LRU(Least Recently Used) – 最近最少使用的:移除最长时间不被使用的对象。
- FIFO(First in First out) – 先进先出:按对象进入缓存的顺序来移除它们。
- SOFT – 软引用:移除基于垃圾回收器状态和软引用规则的对象。
- WEAK – 弱引用:更积极地移除基于垃圾收集器状态和弱引用规则的对象。默认的是 LRU。
flushInterval
属性:缓存自动刷新的时间间隔,单位毫秒。
- 默认情况是不设置,也就是没有刷新间隔,缓存仅仅调用SQL语句时刷新
size
属性:引用数目,正整数。
- 代表缓存最多可以存储多少个对象,太大容易导致内存溢出
readOnly
属性:只读,true/false
- true:只读缓存;会给所有调用者返回缓存对象的相同实例。因此这些对象不能被修改。这提供了很重要的性能优势。
- false:读写缓存;会返回缓存对象的拷贝(通过序列化)。这会慢一些,但是安全,因此默认是false。
<!-- 设置二级缓存 --> <cache> <property name="eviction" value="LRU"/> <!-- 缓存回收策略 --> <property name="flushInterval" value="5000"/> <!-- 自动刷新时间间隔 --> <property name="size" value="1024"/> <!-- 最大缓存1024g个引用对象 --> <property name="readOnly" value="true"/> <!-- 只读 --> </cache>
重用cache标签配置
在命名空间中共享相同的缓存配置和实例,可以使用cache-ref 元素来引用另外一个缓存。
例如:
<!--需要指定Mapper映射文件的位置--> <cache-ref namespace="com.evan.mapper.DeptMapper"/>
MyBatis缓存查询的顺序
执行查询操作时:
- 先查询二级缓存,因为二级缓存中可能会有其他sqlSession已经查出来的数据,这样可以直接获取。
- 如果二级缓存没有查询的数据,再查询一级缓存
- 如果一级缓存也没有查询的数据,则查询数据库
- SqlSession关闭之后,一级缓存中的数据会写入二级缓存
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南