并发场景下saveOrUpdate导致的更新“不成功”的bug

前两天碰到一个好玩的BUG

最终没想到是因为

用了这个saveOrUpdate导致的

我觉得挺好玩

写个demo给大家分享一下

首先呢这是两个spring的

事件监听

他们就都监听了同一个

这个自定义的事件

就这个用户过期

然后呢都采用了这个异步的处理

也就说当事件被触发的时候呢

他们这两个方法几乎是同时运行的

触发的地方就是这个controller

什么也没做哈就触发这个事件

问题是什么呢

我们再来看一下这个listener

这个listener

他主要是

想对指定的这个用户啊

把他的status改成

默认的话他现在是

但是呢

执行完之后这个数据库怎么也不变

我们来试一下哈

然后呢我们来看一下刷新

打开还是

那原因是什么呢

其实很简单

就是在我们触发这个事件的时候

有另一个监听

就是我们刚才看到的这个监听

他也在对同样的一行数据进行更新

只不过呢

他们两个属于不同的业务逻辑

所以呢我们拆分了两个listener

但是呢

他们都是对同一条数据进行更新

更新的字段不一样

比如这个他是更新的这个days

他是想先把这个用户查出来

然后根据这个用户的信息呢

计算出一个结果

把这个结果更新到字段里面

因为他这个计算

依赖于用户的这些属性

所以呢他要先把这个查出来

那问题就在于最后这saveOrUpdate

因为他在最后执行这项代码的时候

这个user已经不是最新版的了

也就是说他查的时候这个user

还是10 但是呢

等他走到这的时候

数据库里边已经

被另一个线程改成99了

但是他这个listener

这个地方没感知到,(所以)它还是10嘛

所以呢他又把10覆盖回去了

也就是说,这并不是没有更新成功

而是一瞬间又被覆盖回去了

所以我们解决这个问题其实

知道了就很容易

就user

我们可以第一种方法就把这个status

设置成null

这样的话呢

saveOrUpdate的时候就会忽略这个字段

对吧我们来试

我们再来请求一下

大家更新完成

我们再来刷新一下

大家看 这就变成99了

然后第二个方法呢

就是我们把它不用这种更新方式了

我们把它改成update wrapper这种方式

就是这种方式

然后呢userService.update把它传进来

这样就没有问题了

因为这样的话呢

他就只更新这一个字段了

最后更新于