mybatis --pageHelper分页数据总条数不对和数据不完整问题分析

问题复现:

我有两张表,1张是学生表,另一张是家属表。学生与家长是一对多关系。现在有个需求,查出每个学生和他的家属的姓名,并分页展示。关联关系是,学生fa_id作为学生表的主键,家长表内通过学生fa_id与该学生关联。

查询方式是:
学生表fb left join 家长表 fp on fb.fa_id = fp.fa_id
然后由mybatis根据嵌套了collection的resultMap来将数据进行封装。(问题就出在嵌套的resultMap上)

传输对象如下:

public class UserBasicInfoTo {
    private String faId; //学生id
    private String fbName; // 学生名
    private String fbPhone; //学生电话号码
    private List agedPeople; //学生家属名列表

    public UserBasicInfoTo() {
    }
//getters and setters
}

xml配置如下:

    
        
        
        
        
            
        
    


    

对应的mapper接口

@Repository
public interface InsFamilyBaseMapper extends BaseMapper {
    List getUserInfoTos(@Param("sysId") String sysId, @Param("fbName") String fbName, @Param("fbPhone") String fbPhone);
}

service代码片段

        Integer pageNum = Integer.parseInt(map.get("pageNum"));
        Integer pageSize = Integer.parseInt(map.get("pageSize"));
        PageHelper.startPage(pageNum, pageSize);
        // join两张表,UserBasicInfoTo嵌套一个List
        List tos = insFamilyBaseMapper.getUserInfoTos(sysId, fbName, fbPhone);

        PageInfo pageInfo = new PageInfo<>(tos);
        return pageInfo;

上述代码在返回pageInfo时,结果的总条数不对。查出来的对象个数为13,但是pageInfo却显示有19条记录,如下所示:


查询到的vos一共13条,但pageInfo的total却为19

这里明显是不对的,但哪里出问题了呢?
在讲resultSetHandler的时候,明确提到过,它对有嵌套的resultMap的处理时,做了一次ensureNoRowBounds的检查,即确保他自己没有分页参数。

也就是说,在mybatis的resultSetHandler处理原始带有嵌套的resultMap时,是没有带分页参数的。那么这个时候pageHelper是怎么工作的呢?
我们实现一个interceptor拦截打印一下执行的sql,就了然了。

1.把pageHelper注释掉


不用pageHelper

不带pageHelper时的执行语句

sql查到了19条记录,此时并没有封装成UserBasicInfoTo,即数据库中有19条记录是满足查询条件的。
然后resultSetHandler对结果集进行处理封装,对相同fa_id的结果集进行合并处理(相同fa_id的结果集都封装成1个infoTO对象,对嵌套的resultMap处理并放入到该对象的List agedPeople中。)所以处理完成之后就剩13个对象了.如图

image.png

现在明白了,19是原始的记录数,13是resultSetHandler封装后的结果数。

那么为什么pageHelper能得到总的记录数呢?
原因是他做了这样一个操作:拦截了当前执行的sql语句,并通过select count(0)语句得到了符合查询条件的记录数,然后将其作为总页数,即19。


image.png

然后,对sql语句进行拼接,加上分页参数。


这是一张细长的图片

但是mybatis在处理statement的时候,将分页参数提取出来了,存放在了rowBounds中,向数据库发送的query中并没有limit a, b ,而是最后在resultSetHandler处理结果集的时候,跳过了分页参数影响的那些行。
image.png

[图片上传中...(image.png-f4c9f3-1616516429638-0)]

所以,pageHelper对嵌套的resultMap进行分页处理的时候,还有可能会导致嵌套的数据不完整,如Vo中应当包含一个List,该list中如果有数据被分页参数跳过了,那么list内的数据就不完整了。

最后举几个例子来说明pageHelper在处理嵌套的resultMap时,出现上面所说的数据总数和数据不完整的问题。

    1. pageNum =3, pageSize = 5, 转换成sql则是limit 10, 5获取第11-15条记录,假设这里面有3个不同的学生,5个家长。如果这三个学生还有其他满足条件的记录,但这些记录不在第11-15条之间,那么家长信息就不完整了。另外,pageinfo的total是上面所说的select count(0) from xxx where (columns subject to condition),也就是总的记录数,但我现在要的是不同的学生数,所以page中的count也不对了。
    1. 举个极端点的例子来说,pageNum = 2,时,在pageSize = 1和pageSize = 20的两种条件下,查询出来的agedPeople截然不同。


      pageSize = 1

      pageSize = 20

      看出问题来了吗? 现在总记录数是19条,当pageSize = 20的时候,该学生所有的家长都包含在了agedPeople中了。但是pageSize= 1的时候,仅仅包含了第2条记录对应的家长。

总结一下问题就是:我告诉你我要查第i到j个学生的信息,你却给了我数据库第i到j条记录封装成的学生信息。我想要知道我一共有多少个学生符合查询条件,你却告诉我一共有多少条记录满足查询条件。这完全是两码事嘛。

问题的解决办法:避免在使用分页插件的时候用嵌套的resultMap。 如resultMap中去嵌套1个List。
目前可以解决的办法有两种:

  • 1.使用子查询:
    
        
        
        
        
        
        
    

    

    

mapper接口方法、封装数据使用的javaBean也相同。唯一不同的就是select和resultMap了(见上)。

    List getUserBasicInfoTos(@Param("sysId") String sysId, @Param("fbName") String fbName, @Param("fbPhone") String fbPhone);

此时pageNum=2, pageSize =1的结果:由于对学生进行了过滤,共有两个符合条件的学生,当前页显示其中一个人的数据,学生的家长数据完整。


关联查询,得到正确结果
  • 2.分步查询, 先将包含rowKey的主体信息查出来,即先去主表查出能区分不同实体的属性。然后根据主体属性与嵌套的属性关系,查嵌套属性并自己在java代码内完成封装。这种方式很容易理解,就不罗列代码了。

你可能感兴趣的:(mybatis --pageHelper分页数据总条数不对和数据不完整问题分析)