被 MyBatis 缓存坑了,各位可以来了解下

2020-06-11 11:39:27 +08:00
 lxy

Debug 了半天。

SpringBoot + MyBatis,默认配置,你觉得下述代码可能有何问题?

@Transactional
public void some_service(List<Person> persons) {
    for (Person person : persons) {
        List<Card> cards = dao.findByCardIds(person.getCardIds());
        do_something(cards);
    }
}

注意 MyBatis 有缓存机制,在同一 SqlSession 中默认开启,每次非事务查询用的都是新建的 SqlSession,所以缓存不生效。

但当开启事务时,spring 会使用同一个 SqlSession 做查询,此情况下一级缓存生效。

例子中,如果程序对查询结果 list 进行了修改操作,那么缓存中的 list 也会相应变化。

第二次查询时若参数相同(比如 for 循环的是两个相同的 person ),会将缓存中的已被修改过的 list 取出。

取出的污染数据可能导致后续代码流程错误。

解决方法是在 Mapper 设置 flushCache="true"。

总结一下设计缺陷:

  1. 默认配置下,缓存表现不一致。如果非事务查询无缓存,那么事务性查询也应该无缓存。

  2. 缓存未做读取时拷贝( copy on read )。由于缓存返回的是结果集的引用,如果后续代码修改了结果集,将导致缓存污染。

3530 次点击
所在节点    Java
12 条回复
cubecube
2020-06-11 11:52:26 +08:00
这不叫被坑,这叫没搞清楚机制,系统出了 bug
wysnylc
2020-06-11 11:53:42 +08:00
xuanbg
2020-06-11 11:54:36 +08:00
查询瞎用什么事务……
PopRain
2020-06-11 12:08:50 +08:00
事务里面,难道不应别人不能修改,用缓存的没有错。。。。 不要乱开事务
lxy
2020-06-11 12:09:49 +08:00
@cubecube 这是设计有坑

@wysnylc 我说的是一级缓存,默认开启的

@xuanbg 只是举个例子,不是实际情况
luckyrayyy
2020-06-11 12:30:05 +08:00
这是设计特性,不是 bug 啊...
aragakiyuii
2020-06-11 12:46:04 +08:00
这不是设计缺陷
如果业务上 cardId 会重复的话应该在循环外面把 cardId 取出来放到 set 里,再去循环 cardId set
DJQTDJ
2020-06-11 13:22:09 +08:00
如果是 bug 的话,就不会公开 flushCache="true"的方法了
dayformyjob
2020-06-11 14:09:56 +08:00
每次查询用最新的,flushCache="true",或者 useCache="false"
YoRuo
2020-06-11 17:45:06 +08:00
不是 bug 。。。。
BBCCBB
2020-06-11 18:13:04 +08:00
是 feature
BBCCBB
2020-06-11 18:14:58 +08:00
在高级别的事务隔离级别下, 是有可重复读的特性的, mybatis 事务缓存了同样的查询

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/680609

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX