
Debug 了半天。
SpringBoot + MyBatis,默认配置,你觉得下述代码可能有何问题?
@Transactional public void some_service(List<Person> persons) { for (Person person : persons) { List<Card> cards = dao.findByCardIds(person.getCardIds()); do_someting(cards); } } 注意 MyBatis 有缓存机制,在同一 SqlSession 中默认开启,每次非事务查询用的都是新建的 SqlSession,所以缓存不生效。
但当开启事务时,spring 会使用同一个 SqlSession 做查询,此情况下一级缓存生效。
例子中,如果程序对查询结果 list 进行了修改操作,那么缓存中的 list 也会相应变化。
第二次查询时若参数相同(比如 for 循环的是两个相同的 person ),会将缓存中的已被修改过的 list 取出。
取出的污染数据可能导致后续代码流程错误。
解决方法是在 Mapper 设置 flushCache="true"。
总结一下设计缺陷:
默认配置下,缓存表现不一致。如果非事务查询无缓存,那么事务性查询也应该无缓存。
缓存未做读取时拷贝( copy on read )。由于缓存返回的是结果集的引用,如果后续代码修改了结果集,将导致缓存污染。
1 cubecube 2020 年 6 月 11 日 via Android 这不叫被坑,这叫没搞清楚机制,系统出了 bug |
2 wysnylc 2020 年 6 月 11 日 |
3 xuanbg 2020 年 6 月 11 日 查询瞎用什么事务…… |
4 PopRain 2020 年 6 月 11 日 事务里面,难道不应别人不能修改,用缓存的没有错。。。。 不要乱开事务 |
6 luckyrayyy 2020 年 6 月 11 日 这是设计特性,不是 bug 啊... |
7 aragakiyuii 2020 年 6 月 11 日 via Android 这不是设计缺陷 如果业务上 cardId 会重复的话应该在循环外面把 cardId 取出来放到 set 里,再去循环 cardId set |
8 DJQTDJ 2020 年 6 月 11 日 如果是 bug 的话,就不会公开 flushCache="true"的方法了 |
9 dayformyjob 2020 年 6 月 11 日 每次查询用最新的,flushCache="true",或者 useCache="false" |
10 YoRuo 2020 年 6 月 11 日 不是 bug 。。。。 |
11 BBCCBB 2020 年 6 月 11 日 是 feature |
12 BBCCBB 2020 年 6 月 11 日 在高级别的事务隔离级别下, 是有可重复读的特性的, mybatis 事务缓存了同样的查询 |