参考:
https://blog.csdn.net/qq_24905875/article/details/119330997
缓存惹的祸?探讨MyBatis二级缓存的四个不推荐之处
https://blog.csdn.net/m0_49768044/article/details/131472652
聊聊MyBatis缓存机制
https://blog.csdn.net/Andy2019/article/details/79664872
众所周知,Mybatis 的一级缓存是 sqlSession 级别的,二级缓存是 sqlSessionFactory 级别的。
然而,在我们的代码中,根本找不到SqlSession、sqlSessionFactory 相关的东西。那么,Mybatis 的一二级缓存该如何命中呢?
一级缓存:
默认情况下,mybatis 开启并使用了一级缓存。
/**
* 开启事务,测试一级缓存效果
* 缓存命中顺序:二级缓存---> 一级缓存---> 数据库
**/
@Test
@Transactional(rollbackFor = Throwable.class)
public void testFistCache(){
// 第一次查询,缓存到一级缓存
userMapper.selectById(1);
// 第二次查询,直接读取一级缓存
userMapper.selectById(1);
}
执行以上代码,虽然进行了两次查询,但最终只请求了一次数据库,显然,第二次查询命中了一级缓存,直接返回了数据。
说明一下:由于使用了数据库连接池,默认每次查询完之后自动 commite,这就导致两次查询使用的不是同一个 sqlSessioin,根据一级缓存的原理,它将永远不会生效。当我们开启了事务,两次查询都在同一个 sqlSession 中,从而让第二次查询命中了一级缓存。
二级缓存
默认情况下,Mybatis 打开了二级缓存,但它并未生效,因为二级缓存的作用域是 namespace,所以还需要在 Mapper.xml 文件中配置一下才能使二级缓存生效。
下面对 UserMapper.xml 配置一下,让其二级缓存生效,只需加入 cache 标签即可。
<cache />
测试代码如下:
/**
* 测试二级缓存效果
* 需要*Mapper.xml开启二级缓存
**/
@Test
public void testSecondCache(){
userMapper.selectById(1);
userMapper.selectById(1);
}
两次查询,最终只请求了一次数据库,显然,第二次查询直接命中了二级缓存,日志还打印了该缓存的命中率。
问题:慎用二级缓存
二级缓存的作用域是一个 namespace ( 一般情况下一个 namespace 即一个 Mapper )
<?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.zhengxl.mybatiscache.mapper.userMapper">
</mapper>
二级缓存不建议使用,因为二级缓存有严重的使用问题:
例如:
MapperA 联合查询AB表中数据
MapperA:select from tableA left join tableB on ……
MapperB 修改了数据
MapperB:insert into tableB values ……
由于 MapperA 和 MapperB 不在同一个作用域,即使 MapperB 新增了数据,MapperA 缓存也不会刷新,造成 MapperA 查到的数据是脏数据。
当然,解决办法也是有的,可以使用 cache-ref 标签合并两个 Mapper 为同一个 namespace。
配置 UserMapper.xml
<cache-ref namespace="com.zhengxl.mybatiscache.mapper.UserOrderMapper"/>
1
但随着Left Join … ON 的需求变多,namespace 将变得越来越难以维护。
而且,如果应用是分布式部署,由于二级缓存存储在本地,必然导致查询出脏数据,所以,分布式部署的应用不建议开启。