工作中被同事提出一个bug,跟踪了一下发现这个bug不是每次都能重现,几番周折,发现是我代码本身的写法和mybatis中一个配置凑巧碰到了一块出现的这个问题。
先看代码的写法:(这中写法就是为了不用每次都创建对象)
User user = new User();
user.setId(x);
user = dao.get(user);
...
user.setId(y);
user = dao.get(user);
...
再来看一下mybatis配置中的那一项就是localCacheScope。
先来认识一下localCacheScope是什么:
MyBatis 利用本地缓存机制(Local Cache)防止循环引用(circular references)和加速重复嵌套查询。 默认值为 SESSION,这种情况下会缓存一个会话中执行的所有查询。 若设置值为 STATEMENT,本地会话仅用在语句执行上,对相同 SqlSession 的不同调用将不会共享数据。
项目中使用的是默认设置,那就是会话中查询数据共享,项目是一个流程流转的操作,流转到下一步时,除了指定下一步处理人,还需要额外设置五个评委,为方便讲述我暂且把下一步处理人叫做A,发现提交一下步之后,这个处理人有的时候不是A,而是其中一个评委,这个问题原因隐藏比较深,现在一点一点分析:
在组织参数之前,这六个人每一个人都需要调用同一个dao的方法查询,如果这五个评委中没有A这个人的时候,那这个流程流转不会出现问题,因为这六次查询的参数每次都不一样,当这个A就是其中一个评委的时候,那就出现上面说的问题了(这六次调用dao方法的顺序是先五个评委,最后才是A,其实这个A在五个评委中的排序不是最后一个的时候才会出现,如果是最后一个也不会出现),debug发现的时候会发现,我调试的时候A在评委中排序是第四个,这个五个评委五次查询都会调用dao方法,最后执行A的时候,本应该也调用dao查询,但是发现没有调用,并且A对象中的查询条件属性变了,这个变的值就是最后一个评委对应属性的值,其他的属性是对的。这样让大家看可能不是很明白,下面我个举个具体的例子:
假设每一个人的对象是User,dao方法是根据id属性当条件查询
第四次查询 评委4(A这个人) User(id=1,name=null) => 调用dao方法 => User(id=1,name=1)
user.setId(2);//根据上面的localCacheScope配置,这个user对象其实已经被缓存了下来,此时user还指向了id为1查询出来的对象,但是这一步已经把id改成2了,这里改的是内存中被缓存下来的对象。
第五次查询 评委5 User(id=2,name=null) => 调用dao方法 => User(id=2,name=2)
user.setId(1);//这次参数还是1,由于已经被mybatis缓存了下来,所有不会调用sql查询,直接返回缓存下来对象,也就是上面被修改的对象。
第六次查询 A这个人 User(id=1),name=null => 本地缓存机制,不调用dao => User(id=2,name=1)
所以就导致了最后一次查询是这个结果。
虽然这次的问题和代码写法有关系,但是这种缓存机制确实也有一定的危险性,最后决定还是把这个localCacheScope改成了STATEMENT。
转载于:https://blog.51cto.com/wangguangshuo/1957303