MyBatis的一级缓存、二级缓存及脏读
MyBatis的缓存分为一级缓存和二级缓存,一级缓存默认打开且无法关闭,二级缓存需要手动打开。不管一级缓存还是二级缓存,都存在脏读的情况。
一级缓存支持SqlSession级别,二级缓存能支持到多个SqlSession,且在同一个namespace下面。
一级缓存
默认打开,作用域是SqlSession。即同一个SqlSession对同一个查询sql进行查询,会将第一次的查询数据放入到缓存中,而第二次会直接从缓存中拿取,不在查询数据库。
但是当如下情况缓存会失效:
- 查询条件不同,即执行的查询sql不同
- 同一个事物中执行了增删改操作
- 手动清除缓存
测试代码(使用springboot+mybatis):
1 2 3 4 5 6 | @Transactional @Override public void findById(Serializable id) { dicMapper.selectById(id); dicMapper.selectById(id); } |
在springboot中需要开启事物@Transactional才能使一级缓存生效,原因在于需要在同一个事物里。
可以看出两次查询,只发送了一次sql。第二次查询会从缓存中进行查找。
缓存失效
测试代码
1 2 3 4 5 6 7 | @Transactional @Override public void findById(Serializable id) { Dic dic = dicMapper.selectById(id); //执行增删改,使缓存失<br> dicMapper.updateById(dic); Dic dic2 = dicMapper.selectById(id); } |
结果
二级缓存
需要手动开启,作用域是namespace,支持这个namespace下不同的SqlSession。
开启步骤:
默认开启了cacheEnabled属性,默认值是true
需要在指定的Mapper上指定开启
1 2 3 | // 开启二级缓存 @CacheNamespace public interface DicMapper extends BaseMapper<Dic> { |
且指定的POJO需要实现序列化:
public class Dic implements Serializable
完成以上步骤,就代表在DicMapper 这个namespace下开启了二级缓存。
但是当如下情况缓存会失效:
1. 在过程中执行了增删改操作
测试代码
DicMapper.java
1 2 3 4 5 6 7 | @CacheNamespace public interface DicMapper extends BaseMapper<Dic> { @Select ( "select * from sys_dic" ) public List<Dic> getAll(); } |
TestController.java
1 2 3 4 5 6 | @GetMapping ( "/allDic" ) public Dic getAllDic(){ List<Dic> all1 = dicMapper.getAll(); List<Dic> all2 = dicMapper.getAll(); return null ; } |
输出结果:
对于第二次查询,发现没有走数据库进行查询。
缓存失效
测试代码:
1 2 3 4 5 6 7 8 | @GetMapping ( "/allDic" ) public Dic getAllDic(){ List<Dic> all1 = dicMapper.getAll(); // 过程中执行增删改操作使缓存失效 dicMapper.deleteById( 298 ); List<Dic> all2 = dicMapper.getAll(); return null ; } |
输出结果:
缓存下的脏读
虽然MyBatis提供了一级缓存和二级缓存,但是如果在特殊情况下会出现数据读取错误问题,导致读取的数据并不是当下真实的数据。
一级缓存脏读
在同一事物下,使用了不同的SqlSession进行操作时,就会发生脏读;
测试代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | @Transactional public void dirtyData(Serializable id) { Dic dic1 = dicMapper.selectById(id); System.out.println( "第一次查询DIC:\t" +dic1); SqlSession sqlSession = sqlSessionFactory.openSession( true ); DicMapper mapper = sqlSession.getMapper(DicMapper. class ); Dic dic2 = mapper.selectById(id); System.out.println( "重新开启SqlSession查询DIC:\t" +dic2); dic1.setName(dic1.getName()+ "-edit" ); dicMapper.updateById(dic1); System.out.println( "使用第一次的Mapper进行数据修改" ); Dic dic2_1 = dicMapper.selectById(id); System.out.println( "使用第一次的Mapper重新查询数据(此刻会查询到正确数据):\t" +dic2_1); Dic dic2_2 = mapper.selectById(id); System.out.println( "使用开启SqlSession重新查询数据(此刻会出现脏读):\t" +dic2_2); } |
测试结果:
二级缓存脏读
使用了不同的Mapper(nameSpace)操作了同一数据对象就会出现脏读
测试代码:
Dic2Mapper.java 创建一个新的Mapper但是也操作Dic对象
1 2 3 4 5 | @CacheNamespace public interface Dic2Mapper extends BaseMapper<Dic> { } |
service层:
1 2 3 4 5 6 7 8 9 10 11 | public void dirtyDataTwo(Serializable id) { Dic dic = dicMapper.selectById(id); System.out.println( "dicMapper第一次查询:\t" +dic); System.out.println( "dicMapper2第一次查询:\t" +dicMapper2.selectById(id)); dic.setName( "-tow-edit" ); dicMapper.updateById(dic); System.out.println( "使用dicMapper进行更新操作" ); Dic dic2 = dicMapper.selectById(id); System.out.println( "dicMapper第二次查询:\t" +dic2); System.out.println( "dicMapper2第二次查询:\t" +dicMapper2.selectById(id)); } |
输出结构:
缓存优先级的使用
从大到小优先执行:
二级缓存 -> 一级缓存 -> 数据库
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· 【自荐】一款简洁、开源的在线白板工具 Drawnix