Mybatis查询:结果集的顺序引起的数据缺失和重复的坑
上一篇文章遇到一个问题:Mybatis多表关联内连接和左连接结果不一致
详情请看文章链接: https://blog.****.net/qq_18259401/article/details/80025869
最后问了好些人都不知道原因,网上也搜不到,无奈花了三个多小时debug看源码才发现问题,而且这个问题还是源码引起的,如下:
1.使用结果错误的内连接查询语句dubug情况如下:
在源码dubug到org.apache.ibatis.executor.resultset.NestedResultSetHandler 类中如下方法:
这个方法的作用大概就是:根据结果集中查询到的数据装配到返回的实体类属性中
在调用该方法时参数ResultSet rs中已经存在查询结果如下:
因不好截图,我这里文字说明ResultSet中结果(主要是结果集的顺序):
如上图,结果中有10条数据,顺序依次如下(相应列出对应的userID和gradeID):
rs[0] --->49 userID=1 gradeID=2
rs[1] --->51 userID=3 gradeID=1
rs[2] --->52 userID=4 gradeID=2
rs[3] --->53 userID=5 gradeID=2
rs[4] --->54 userID=6 gradeID=2
rs[5] --->55 userID=7 gradeID=2
rs[6] --->56 userID=8 gradeID=2
rs[7] --->50 userID=2 gradeID=1
rs[8] --->50 userID=2 gradeID=2
rs[9] --->51 userID=3 gradeID=2
上面的handleRowValues方法在执行处理ResultSet结果集的顺序如下:
83行先将rs中下一个userID给rowKey,我这里第一条数据userID是1,所以rowKey=1
84行去objectCache中查询rowKey=1的user是否存在,此时objectCache和rowValue都还是没有数据的,因此85行条件不成立,所以不会运行87行(87行是将user装配到resultHandler中,我们最后的结果就是从resulthandler中取的)
89行,会根据rowKey将userID=1的用户查出来放到objectCache中,且将查出的user对象赋值给rowValue
第一次循环结束,rowKey=1,rowValue=user1 ,objectCache中也只有一个user1,resultHandler中没有数据
第二次执行while循环,83行中将rs中第二个userID给rowKey,此时rowKey=3
84行去objectCache中查询rowKey=3的user没有查到且rowValue此时是user1不为null,
因此85行条件成立会执行87行,87行将rowValue中user1装配到resultHandler中
再执行89行,根据rowKey=3查询将结果user3放到objectCache中,且之后rowValue=user3
第二次循环结束,rowKey=3,rowValue=user3 ,objectCache中有user1和user3,resultHandler中有user1
第三次执行while循环,83行中将rs中第3个userID给rowKey,此时rowKey=4
84行去objectCache中查询rowKey=4的user没有查到且rowValue此时是user3不为null,
因此85行条件成立会执行87行,87行将rowValue中user3装配到resultHandler中
再执行89行,根据rowKey=4查询将结果user4放到objectCache中,且之后rowValue=user4
第三次循环结束,rowKey=4,rowValue=user4 ,objectCache中有user1和user3和user4,resultHandler中有user1和user3
......依次循环到第七次结束后:rowKey=8,rowValue=user8,
objectCahce中数据有:user1,user3,user4,user5,user6,user7,user8
resulthandler中数据有:user1,user3,user4,user5,user6,user7
第八次循环,83行中将rs中第8个userID给rowKey,此时rowKey=2,
84行去objectCache中查询rowKey=2的user没有查到且rowValue此时是user8不为null,
因此85行条件成立会执行87行,87行将rowValue中user8装配到resultHandler中
再执行89行,根据rowKey=2查询将结果user2放到objectCache中,且之后rowValue=user2
第八次循环结束,rowKey=2,rowValue=user2 ,
objectCache中有:user1,user3,user4,user5,user6,user7,user8,user2
resultHandler中有:user1,user3,user4,user5,user6,user7,user8
第九次循环,83行中将rs中第8个userID给rowKey,此时rowKey=2,
84行去objectCache中查询rowKey=2的user发现user2存在
因此85行条件不成立不会执行87行,
再执行89行,根据rowKey=2查询发现objectCache中存在user2,则user2的List<grade>属性添加新的grade对象(详情请看上篇文章),之后rowValue=user2
第九次循环结束,rowKey=2,rowValue=user2 ,
objectCache中有:user1,user3,user4,user5,user6,user7,user8,user2(两个grade)
resultHandler中有:user1,user3,user4,user5,user6,user7,user8
第十次循环,83行中将rs中第8个userID给rowKey,此时rowKey=3,
84行去objectCache中查询rowKey=3的user发现user3存在
因此85行条件不成立不会执行87行,
再执行89行,根据rowKey=3查询发现objectCache中存在user3,则user3的List<grade>属性添加新的grade对象(详情请看上篇文章),之后rowValue=user3
第十次循环结束,rowKey=3,rowValue=user3 ,
objectCache中有:user1,user3(两个grade),user4,user5,user6,user7,user8,user2(两个grade)
resultHandler中有:user1,user3(两个grade),user4,user5,user6,user7,user8
此时循环已经结束,执行循环外第91行,发现rowValue=user3不为空,则将rowValue中user3装配到resultHandler中,
此时装配结束后:rowKey=3,rowValue=user3 ,
objectCache中有:user1,user3(两个grade),user4,user5,user6,user7,user8,user2(两个grade)
resultHandler中有:user1,user3(两个grade),user4,user5,user6,user7,user8,user3(两个grade)
而最终的结果是根据resultHandler中结果返回。
所以最后的我查询获取到的结果错误如下:
UserID: 1 UserName: 李四 UserPW: lisi UserAge: 24 size: 1
------ GradeID: 2 GradeIden: 普通用户 GrdeJurs: 查
UserID: 3 UserName: 王武 UserPW: wangwu UserAge: 23 size: 2
------ GradeID: 1 GradeIden: 管理员 GrdeJurs: 增删改查
------ GradeID: 2 GradeIden: 普通用户 GrdeJurs: 查
UserID: 4 UserName: 赵柳 UserPW: zhaoliu UserAge: 24 size: 1
------ GradeID: 2 GradeIden: 普通用户 GrdeJurs: 查
UserID: 5 UserName: 陈琦 UserPW: chenqi UserAge: 23 size: 1
------ GradeID: 2 GradeIden: 普通用户 GrdeJurs: 查
UserID: 6 UserName: 张八 UserPW: zhangba UserAge: 23 size: 1
------ GradeID: 2 GradeIden: 普通用户 GrdeJurs: 查
UserID: 7 UserName: 曹久 UserPW: caojiu UserAge: 22 size: 1
------ GradeID: 2 GradeIden: 普通用户 GrdeJurs: 查
UserID: 8 UserName: 刘师 UserPW: liushi UserAge: 25 size: 1
------ GradeID: 2 GradeIden: 普通用户 GrdeJurs: 查
UserID: 3 UserName: 王武 UserPW: wangwu UserAge: 23 size: 2
------ GradeID: 1 GradeIden: 管理员 GrdeJurs: 增删改查
------ GradeID: 2 GradeIden: 普通用户 GrdeJurs: 查
刚开始查询后的ResultSet类型中的rs对象值是正确的:1345678223,而装配结束后正确结果应该是:13456782,但最后的查询结果却是错误的:13456783
而当我使用左连接查询时,ResultSet中值为:1223345678,该代码装配时在循环执行第四次时判断user3在objectCache中不存在,而rowValue此时是user2,所以85行条件成立会执行87行,会将user2装配进resultHandler中,所以结果正确为:12345678
原因总结:上面结果错误的原因主要是因为最后一个user2在需要装配到resultHandler时没有装配进去,因为user3在之前已经装配进objectCache中,所以在判断下一个userID=3时objectCache已有,则不会执行if内的语句,即不会将rowValue=user2装配进resultHandler中。而无论如何循环结束后在循环外最后一条数据user3都会被装配进resultHandler中,所以user3在结果中是重复的
技术总结:所以在使用mybatis查询时查询结果的顺序也会影响最终的顺序,尽量避免存在如我上面的数据形式,在查询时去重或者尽量使用排序,在主外表选择时要注意主表的数据是否存在如下数据顺序:ABCDDB(结果为ABCB,缺少D且B重复);ABCDB(结果为ABCB,缺少D且B重复);ABCDDBEF(结果为ABCEF,缺少D);如果存在,则在sql语句中需要对该结果顺序进行处理,不然会出错。
个人觉得该代码处理确实算是个坑,所以以后还得注意!!!