摘要:简述mybatis的二级缓存中的参照缓存和脏数据问题
1、上篇帖子简述了mapper接口和xml文件单独某一个开启缓存的配置(https://blog.youkuaiyun.com/wrongyao/article/details/86476486),有时候需要同时为两者开启缓存,这个时候就需要用到参照缓存,配置如下
mapper接口依赖xml的缓存空间
@CacheNamespaceRef(AreaDictMapper.class)
public interface AreaDictMapper {
<mapper namespace="com.honor.mapper.AreaDictMapper">
<!--开启二级缓存-->
<cache/>
上述配置是areaDictMapper接口中注解方法和xml文件中的方法使用相同的缓存,会使用com.honor.mapper.AreaDictMapper命名空间的缓存配置。
xml接口依赖mapper的缓存空间
@CacheNamespace
public interface AreaDictMapper {
<!--开启二级缓存-->
<cache-ref namespace="com.honor.mapper.AreaDictMapper"/>
xml文件参照mapper配置的缓存配置。这种配置必须把xml和mapper始行路径分离(即mapper接口和xml文件不能在同一个包中具体方法参考https://www.cnblogs.com/deolin/p/8195565.html)
2、脏数据现象
2.1 前言:二级缓存是依赖于某个命名空间下的,mapper.xml配置缓存就是mapper的namespace,mapper接口就是接口的全限定名称,在某个命名空间下执行dml操作会清空该命名空间下的缓存信息。
根据上述内容可知,对于单表操作而言,只要开发规范(在同一个命名空间下开发,或者同时使用mapper接口和xml时设置参照缓存,不在后台直接操作数据),是不会出现脏数据的情况的。
但是实际情况中一般都不是单表操作,通常是多表连接查询,而这个查询的statementId是会挂载到某个mapper接口下的。
举个例子A表,AMapper,B表 BMapper,A,B表做连接查询,statementId放在AMapper下
如果AMapper执行dml语句,AMapper下的二级缓存会被清空,没有问题;
如果BMapper执行dml语句,AMapper下的二级缓存不会被清空,脏数据。
解决方法:让AMpper和BMapper参照同一个缓存空间。
2.2 现象描述
三张表sys_user(用户表),sys_user_role(用户权限表)和sys_role(权限表)
模拟业务需求:获取某个用户下的所有权限
sql及执行结果:

现在新建:SysUserMapper和SysRoleMapper以及对应的xml,将上述语句挂载到SysUserMapper.xml中
<mapper namespace="com.honor.mapper.SysUserMapper">
<select id="selectRolesByUserId" resultType="com.honor.model.SysRole">
SELECT
sr.sys_role_id sysRoleId,
sr.role_name roleName
FROM
`sys_user` su
JOIN `sys_user_role` sur ON su.sys_user_id = sur.sys_user_id
JOIN `sys_role` sr ON sur.sys_role_id = sr.sys_role_id
WHERE su.sys_user_id = #{userId};
</select>
</mapper>
分别对SysUserMapper和SysRole开启缓存
@CacheNamespaceRef(SysUserMapper.class)
public interface SysUserMapper {
<mapper namespace="com.honor.mapper.SysUserMapper">
<!--开启二级缓存-->
<cache/>
@CacheNamespaceRef(SysRoleMapper.class)
public interface SysRoleMapper {
<mapper namespace="com.honor.mapper.SysRoleMapper">
<cache/>
脏数据现象:
测试代码:在第二个sqlsession中删除一条sysrole,理想情况下是要更新缓存的,不然会查到已删除的数据,但实际没有。
System.out.println("二级缓存测试开始");
SqlSession sqlSession = sqlSessionFactory.openSession();
SysUserMapper sysUserMapper = sqlSession.getMapper(SysUserMapper.class);
List<SysRole> sysRoles = sysUserMapper.selectRolesByUserId(33);
for (SysRole sysRole : sysRoles) {
System.out.println(sysRole);
}
sqlSession.commit();
sqlSession.close();
System.out.println("开启一个新的sqlsession");
SqlSession sqlSession1 = sqlSessionFactory.openSession();
SysUserMapper sysUserMapper1 = sqlSession1.getMapper(SysUserMapper.class);
// 删除角色数据
SysRoleMapper sysRoleMapper = sqlSession1.getMapper(SysRoleMapper.class);
sysRoleMapper.deleteSysRoleById(10);
List<SysRole> sysRoles1 = sysUserMapper1.selectRolesByUserId(33);
for (SysRole sysRole : sysRoles1) {
System.out.println(sysRole);
}
sqlSession1.commit();
sqlSession1.close();
结果:只发出一条sql,缓存没有被刷新

2.3 解决办法,让SysUserMapper和SysRoleMapper公用一个缓存空间,让SysRoleMapper参照SysUserMapper(反过来也行,保证同一个就可以)配置如下:
@CacheNamespaceRef(SysUserMapper.class)
public interface SysRoleMapper {
<mapper namespace="com.honor.mapper.SysRoleMapper">
<cache-ref namespace="com.honor.mapper.SysUserMapper"/>
结果:

删除sys_role数据也会刷新缓存,重新发送sql,这样就不会有脏数据出现了
3、总结
mybatis的二级缓存可以减轻数据库压力,加快查询,但同时也可能会出现脏数据,解决方法是设置参照缓存,但是如果有很多表连接的话,其实也不太适合用二级缓存,因为参照缓存维护起来也很繁琐,容易出错。
本文深入探讨MyBatis的二级缓存机制,重点讲解参照缓存的配置方法及其在多表操作中避免脏数据的关键作用。通过实例演示如何在不同Mapper间共享缓存空间,确保数据一致性。
626

被折叠的 条评论
为什么被折叠?



