目录
- 前置
- 生效
- 场景一
- 场景二
- 失效
- 场景一
- 场景二
- 场景三
- 场景四
- 脏数据场景
前置
什么是二级缓存:
一级缓存是基于sqlsession级别, 当一个sqlsession会话结束, 一级缓存也就结束了.
定义一级缓存为局部缓存, 那么二级缓存就是全局全局缓存
二级缓存是基于mapper文件的namespace级别,也就是说多个sqlSession可以共享一个mapper中的二级缓存区域,并且如果两个mapper的namespace 相同,即使是两个mapper,那么这两个mapper中执行sql查询到的数据也将存在相同的二级缓存区域中。
会演示二级缓存生效/失效的场景
项目地址: https://gitee.com/xmaxm/test-code/blob/master/chaim-cache/chaim-mybatis-cache/chaim-mybatis-cache-two/README.md
前置配置:
二级缓存(全局缓存)(namespace级别)
第一步需配置: mybatis-plus.configuration.cache-enabled: true 默认true
第二步: 对应entity需要实现 Serializable
第三步: (对应的 mapper 添加 @CacheNamespace->可配缓存参数, xml 添加 标签) 或者 (mapper 添加 @CacheNamespaceRef, xml 添加 标签->可配缓存参数)
缓存可配置参数: https://mybatis.org/mybatis-3/zh/sqlmap-xml.html#cache
注意点
@CacheNamespace(blocking = true) 属性可避免瞬时流量涌入直接打入数据库. 自定义二级缓存(后续文章会有介绍)方式是不支持该属性的, 需要考虑自己实现
源码部分
感觉要是把源码过一遍, 得从新起一篇文章才行, 后面有需要在写, 偷个懒吧, 哈哈哈哈哈!
源码入口: org.apache.ibatis.mapping.CacheBuilder#build
关键类: org.apache.ibatis.cache.Cache
默认实现: org.apache.ibatis.cache.impl.PerpetualCache
默认淘汰策略: org.apache.ibatis.cache.decorators.LruCache
相关缓存文章
Mybatis的一级缓存
Mybatis的二级缓存 (默认方式)
Mybatis的二级缓存 (Redis方式)
Mybatis的二级缓存 (ehcache方式)
生效
场景一
测试二级缓存生效: 按前置描述配置
使用mybatis plus方法
public void queryingLevelCache() {
LambdaQueryWrapper<SysUser> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.select(SysUser::getUsername, SysUser::getPhone, SysUser::getId);
queryWrapper.last("limit 1");
SysUser sysUsers = sysUserMapper.selectOne(queryWrapper);
log.info("查询成功, 观察日志, id: {}", sysUsers.getId());
sqlSession.clearCache();
SysUser user = sysUserMapper.selectOne(queryWrapper);
log.info("查询成功, 观察日志, id: {}", user.getId());
}
场景二
测试二级缓存生效: 按前置描述配置
使用自定义SQL
public void queryingLevelCache(Integer integer) {
SysUser sysUsers = sysUserMapper.selectHandwritingSql();
log.info("查询成功, 观察日志, id: {}", sysUsers.getId());
SysUser user = sysUserMapper.selectHandwritingSql();
log.info("查询成功, 观察日志, id: {}", user.getId());
}
失效
场景一
测试二级缓存失效: 添加@Transactional, 使其在同一个 SqlSession, 然后手动清除缓存
@Transactional(rollbackFor = Exception.class)
public void queryingLevelCacheFail() {
LambdaQueryWrapper<SysUser> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.select(SysUser::getUsername, SysUser::getPhone, SysUser::getId);
queryWrapper.last("limit 1");
SysUser sysUsers = sysUserMapper.selectOne(queryWrapper);
log.info("查询成功, 观察日志, id: {}", sysUsers.getId());
sqlSession.clearCache();
SysUser user = sysUserMapper.selectOne(queryWrapper);
log.info("查询成功, 观察日志, id: {}", user.getId());
}
场景二
测试二级缓存失效: 当两次查询的方式不一样, 使用mybatis的方法, 以及自定义SQL
同理, 当查询的条件以及查询的内容不一致时也会失效
public void queryingLevelCacheFail(Integer integer) {
LambdaQueryWrapper<SysUser> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.select(SysUser::getUsername, SysUser::getPhone, SysUser::getId);
queryWrapper.last("limit 1");
List<SysUser> sysUsers = sysUserMapper.selectList(queryWrapper);
log.info("查询成功, 观察日志, id: {}", sysUsers.size());
SysUser user = sysUserMapper.selectHandwritingSql();
log.info("查询成功, 观察日志, id: {}", user.getId());
}
场景三
测试二级缓存失效: 当两次查询存在之间, 存在增删改的情况
public void queryingLevelCacheFail(String string) {
SysUser sysUsers = sysUserMapper.selectHandwritingSql();
log.info("查询成功, 观察日志, id: {}", sysUsers.getId());
SysUser sysUser = SysUser.builder()
.username("潇潇")
.email("gmail.com")
.phone("000123")
.password("123456")
.sex(1)
.state(0)
.salt(1234)
.build();
sysUserMapper.insert(sysUser);
log.info("观察新增日志, id: {}", sysUsers.getPassword());
SysUser user = sysUserMapper.selectHandwritingSql();
log.info("查询成功, 观察日志, id: {}", user.getId());
}
场景四
测试二级缓存失效:
xml 的标签指定 flushCache=“true”
注解方式SQL配置: @Options(flushCache = Options.FlushCachePolicy.TRUE)
同理还可以全局配置: 禁用mybatis一级缓存: mybatis-plus.configuration.cache-enabled: false. 默认开始 true
public void queryingLevelCacheFail(Boolean bol) {
SysUser sysUsers = sysUserMapper.selectHandwritingSqlFail();
log.info("查询成功, 观察日志, id: {}", sysUsers.getId());
SysUser user = sysUserMapper.selectHandwritingSqlFail();
log.info("查询成功, 观察日志, id: {}", user.getId());
log.info("-----------自义定SQL的两种失效方式-----------------");
sysUsers = sysUserMapper.selectHandwritingSqlFail2();
log.info("查询成功, 观察日志, id: {}", sysUsers.getId());
user = sysUserMapper.selectHandwritingSqlFail2();
log.info("查询成功, 观察日志, id: {}", user.getId());
}
<!-- flushCache默认false. true: 每次查询走数据库查询(该SQL的二级缓存失效) -->
<select id="selectHandwritingSqlFail" resultType="com.chaim.mybatis.cache.two.entitys.SysUser" flushCache="true">
SELECT username,phone,id FROM sys_user limit 1
</select>
@Options(flushCache = Options.FlushCachePolicy.TRUE)
@Select("SELECT username,phone,id FROM sys_user limit 1")
SysUser selectHandwritingSqlFail2();
脏数据场景
脏数据: 前提开启二级缓存. 在两次查询之间, 做INSERT UPDATE DELETE配置其标签: flushCache=“false”, 不清空缓存,
导致第二条SQL走二级缓存获取的数据还是之前缓存的数据
public void queryingLevelCacheError() {
SysUser sysUser = sysUserMapper.selectHandwritingSql();
log.info("查询成功, 观察日志, id: {}", sysUser.toString());
sysUser.setPhone("999090912");
sysUserMapper.updateHandwritingSql(sysUser);
log.info("观察更新日志, id: {}", sysUser.getPassword());
// 由于updateHandwritingSql配置不清除缓存, user的数据还是之前缓存数据(脏数据)
SysUser user = sysUserMapper.selectHandwritingSql();
log.info("查询成功, 观察日志, id: {}", user.toString());
}
<!-- flushCache默认true.
true: 会导致本地缓存和二级缓存被清空
false: 不会清空本地缓存和二级缓存, 即缓存的数据还是之前的(脏读) -->
<update id="updateHandwritingSql" flushCache="false">
UPDATE sys_user SET phone = #{phone} WHERE id = #{id};
</update>