当前位置: 首页 > news >正文

Mybaits缓存踩的坑

记Mybaits缓存踩的坑

1.问题提出

最近开发一个记录操作前后修改内容的功能,获取修改前数据比较简单,直接从数据库获取,记录修改后的功能也比较简单,直接将用户修改的内容封装成po对象,然后两个比对就可以了,问题就出在这。

2.场景复现

下面是出现问题的代码,简化版(操作分为用户端和管理端):

用户端

public class UserServiceImpl{@Autowiredprivate IUserDao userDao;@Autowiredprivate IUserLogDao dao;@Autowiredprivate StreamAction action;@Override@Transactionalpublic void modifyUser(UserDto dto){Long id = dto.getUserId;//修改前的beanUserBean beforeBean = userDao.queryUser(id);//修改后的beanBeanUtils.copyProperties(dto,beforeBean);//更新和用户有关的内容//....用户信息填充 UserLogBean userLogBean//这里需要修改和用户关联的记录,所以插入一次userDao.insert(userLogBean);//用户端操作流action.request(beforeBean);}
}
  • StreamAction.request
@Override
@Transactional
public void request(UserBean userBean){/*** 获取数据库原内容*/UserBean afterBean = userDao.queryUser(id);//比对返回修改内容ModifyBean bean = modify(userBean,afterBean);//...其他 业务//插入修改后的内容userBeanModifyDao.insert(bean);
}

此时去查看数据内添加的内容,可以看到,修改的部分被正确的比对出来了。

管理端

public class UserServiceImpl{@Autowiredprivate IUserDao userDao;@Autowiredprivate IUserLogDao dao;@Autowiredprivate StreamActionManager action;@Override@Transactionalpublic void modifyUser(UserDto dto){Long id = dto.getUserId;//修改前的beanUserBean beforeBean = userDao.queryUser(id);//修改后的beanBeanUtils.copyProperties(dto,beforeBean);//更新和用户有关的内容//....用户信息填充 UserLogBean userLogBean//管理端操作流action.request(beforeBean);}
}
  • StreamActionManager.request
@Override
@Transactional
public void request(UserBean userBean){/***   获取数据库原内容*/UserBean afterBean = userDao.queryUser(id);//比对返回修改内容ModifyBean bean = modify(userBean,afterBean);//...其他 业务//插入修改后的内容userBeanModifyDao.insert(bean);
}

此时我们去数据库查看内容,你就惊奇发现并没有见到修改的内容。不对啊,我们确实已经修改过了,那为什么数据库里不显示呢?

3.发现问题

带着疑问,我们很容易的想到,是不是两个对象内容一模一样?带着疑问,我们尝试着打印一下用户端和管理端,在进行比对前的两个对象内容:

public void request(UserBean userBean){/*** 获取数据库原内容*/UserBean afterBean = userDao.queryUser(id);System.out.println(userBean);  System.out.println(afterBean); //比对返回修改内容ModifyBean bean = modify(userBean,afterBean);//......
}

用户端

我们在比对修改内容前打印两个bean,结果如下:

cn.example.core.core.bean.UserBean@5e5a2b74
cn.example.core.core.bean.UserBean@5e5a2b75

两个bean不是同一个对象,符合我们的预期,所以用户端正确入库。

我们再来看管理端

管理端

管理端执行结果

cn.example.core.core.bean.UserBean@5e5a2b77
cn.example.core.core.bean.UserBean@5e5a2b77

!!!oi!!!,我们惊奇的发现,这两个对象是一样的,那就奇了怪了,用户端和管理端业务代码甚至基本都一样的,为什么会造成这个原因呢?我们再输出这两个对象的内容

System.out.println(userBean.toString());  
System.out.println(afterBean.toString());

很惊奇的是,这两个对象内容都是修改过的内容,也就是service内通过BeanUtils属性赋值过的内容,那我们mysql里的内容去哪了??我们明明还没更新啊!我们赶紧去数据库看一眼,发现数据库里并没更新。数据库里内容没更新,业务里的bean已经被更新过了,这是为什么?

4.排查问题

我们找到出现问题的原因了,是因为管理端两个对象一样。那为什么会一样呢?

我们在业务逻辑里很容易想到类似的场景,比如我们在使用redis的时候,当redis内有数据时,我们希望走redis返回结果而不是走数据库,以提高查询性能,那会不会两个对象一样也是走了缓存呢?我们通过查询数据库我们知道,mysql的查询也会存在缓存,但是按道理来说,我们最后的结果应该是mysql的内容,应该不会是后面的内容。所以只有一种情况,是mybaits的缓存

5.寻找答案

我们已经确定了是mybaits的缓存导致的问题,但是为什么管理端和用户端还不一样呢?为什么用户端就没有这个问题呢?

我们百度之后发现,mybaits有一、二级缓存之分,二级缓存默认不开启。

哎会不会是用户端的缓存过期了?因为用户端的有一个插入其他表的操作,肯定比管理端慢,对对一定是这个问题,好我们去找度娘,度娘说缓存没有过期时间。好好好这样玩,好好好。

那么问题是什么?我们已经确定不是过期时间的问题了,那我们现在想的就是缓存过期,也就是缓存失效了,我们换个方法去查找内容,“mybatis缓存失效的原因”,我们找到以下结果:

1.不在同一个sqlSession中
2.如果是增删改操作,程序会clear缓存。
3.一级缓存未开启
4.手动清空缓存数据,调用sqlsession.clearCache().
5.更改查询条件

我们一点点往下看:

  1. 不在同一个sqlSession中

同一个事务内会复用同一个sqlSession。

具体我们查看控制台,可以看到第一个sql执行之前,会有一句话

Creating a new SqlSession
Registering transaction synchronization for SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@3663af34]

在之后的sql执行时会有一句话

Fetched SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@3663af34] from current transaction

都是同一个sqlSession

我们业务层面的代码是在同一个事务里,因为没有设置事物传播机制,虽然有两个事物注解,但是最后都在同一个事务那,可以用

String txName = TransactionSynchronizationManager.getCurrentTransactionName();

查看,会发现事务没有失效且都在一个事物内,显然不是这个原因。

  1. 如果是增删改操作,程序会clear缓存。

我们用户端涉及的增删改是另外一张表的,排除

  1. 一级缓存未开启

显然开启了,不然不会有这篇博客

4、5不用看了,都不符合

我们分析完了,仍然没找到原因。那么问题到底在哪呢?

目前最有可能的就是2,但是我们明明更改的是其他表啊,那么不妨,我们就试试改其他表。

@Test
@Transactional  //这里的事务一定要加,mybatis的缓存存在条件就是需要有事务,否则你查询会发现两个对象怎么样都不会相同
public void test(){UserBean beforeBean = userDao.queryUser(id);//更新userLogDao.update("1","1");UserBean afterBean = userDao.queryUser(id);System.out.println(beforeBean);  System.out.println(afterBean); }

哎,神奇,两个对象不一样了,缓存失效了!所以问题就在这,所以这就是造成这个bug的原因,知道了问题,那我们就开始做解决方案

6.解决方案

解决方案有一下几种:

  • 关闭mybatis一级缓存,无法关闭,只能修改状态
mybatis:configuration:cache-enabled: false #禁用二级缓存local-cache-scope: statement #一级缓存指定为statement级别 默认为session级别
  • 使用不同的对象传递

在传递前后bean的时候,用其他的bean赋值传递。

  • 使用不同的查询方式,拼接条件等
  • 使用不同的事物隔离级别:
//todo 

到此为止,我们的问题就解决了。

http://www.lryc.cn/news/188988.html

相关文章:

  • 全国工商注册数据库的作用
  • 【Linux】NTP时间服务器Chrony配置详解
  • 今年的秋招面试,确实有点难。
  • Rn使用FlatList导航栏自动回到中间
  • 单例模式中的线程安全问题
  • Android 13 骁龙相机点击拍照流程分析(一)——点击拍照到更新到左下角缩略图
  • Docker 的网络与数据管理
  • 在QGIS中给矢量数据属性编号的一种方法
  • 对一个变速器原理的分析
  • 秒验:可以自定义UI的一键登录服务
  • pmm最新版本v2.40.0尝鲜体验
  • 2023年中国数据存储市场现状及发展前景预测分析
  • xlsx冻结单元格
  • yolov8剪枝实践
  • 功能基础篇6——系统接口,操作系统与解释器系统
  • 由于导线材质不同绕组直流电阻不平衡率超标
  • 选择智慧公厕解决方案,开创智慧城市公共厕所新时代
  • FFmpeg 基础模块:AVIO、AVDictionary 与 AVOption
  • 代数——第3章——向量空间
  • 2023年软考网工上半年下午真题
  • Flutter 直接调用so动态库,或调用C/C++源文件内函数
  • elasticsearch(ES)分布式搜索引擎03——(RestClient查询文档,ES旅游案例实战)
  • 198、RabbitMQ 的核心概念 及 工作机制概述; Exchange 类型 及 该类型对应的路由规则
  • 系统架构设计:18 论基于DSSA的软件架构设计与应用
  • Android原生实现控件outline方案(API28及以上)
  • ROS学习笔记(六)---服务通信机制
  • 常见的C/C++开源QP问题求解器
  • 前端axios发送请求,在请求头添加参数
  • CTF Misc(3)流量分析基础以及原理
  • Telink泰凌微TLSR8258蓝牙开发笔记(二)