美文网首页
mybatis --pageHelper分页数据总条数不对和数据

mybatis --pageHelper分页数据总条数不对和数据

作者: 布拉德老瓜 | 来源:发表于2021-03-24 00:26 被阅读0次

    问题复现:

    我有两张表,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<String> agedPeople; //学生家属名列表
    
        public UserBasicInfoTo() {
        }
    //getters and setters
    }
    

    xml配置如下:

        <resultMap id="UserInfoToMap" type="com.cloud.fmmp.base.bean.to.UserBasicInfoTo">
            <result column="FB_Name" property="fbName"></result>
            <result column="FA_ID" property="faId"></result>
            <result column="FB_Phone" property="fbPhone"></result>
            <collection property="agedPeople" ofType="java.lang.String">
                <result column="pb_cnname"></result>
            </collection>
        </resultMap>
    
    
        <select id="getUserInfoTos" resultMap="UserInfoToMap">
            SELECT FB_Name, FB_Phone, fb.FA_ID, PB_CNName FROM ins_family_base fb
            LEFT JOIN ins_family_people fp
            ON fb.FA_ID = fp.FA_ID
            WHERE fb.SYS_ID = #{sysId}
            <if test="fbName != null">
                and fb.FB_Name = #{fbName}
            </if>
            <if test="fbPhone != null">
                and fb.FB_Phone = #{fbPhone}
            </if>
        </select>
    

    对应的mapper接口

    @Repository
    public interface InsFamilyBaseMapper extends BaseMapper<InsFamilyBase> {
        List<UserBasicInfoTo> 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<UserBasicInfoTo> tos = insFamilyBaseMapper.getUserInfoTos(sysId, fbName, fbPhone);
    
            PageInfo<UserBasicInfoTo> 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<String> 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.使用子查询:
        <resultMap id="UserBasicInfoToMap" type="com.cloud.fmmp.base.bean.to.UserBasicInfoTo">
            <result column="FB_Name" property="fbName"></result>
            <result column="FA_ID" property="faId"></result>
            <result column="FA_NickName" property="faNickname"></result>
            <result column="FB_Phone" property="fbPhone"></result>
            <!--注意看这里,里面包含一个select子查询-->
            <collection property="agedPeople" javaType="java.util.List" ofType="java.lang.String" column="fa_id = fa_id" select="selectAgedPeople"></collection>
        </resultMap>
    
        <select id="getUserBasicInfoTos" resultMap="UserBasicInfoToMap">
        SELECT FB_Name, FB_Phone, fb.FA_ID, fa.FA_NickName FROM ins_family_base fb
        LEFT JOIN ins_family_account fa
        ON fb.FA_ID = fa.FA_ID
        WHERE fb.SYS_ID = #{sysId}
        <if test="fbName != null">
            and fb.FB_Name = #{fbName}
        </if>
        <if test="fbPhone != null">
            and fb.FB_Phone = #{fbPhone}
        </if>
        </select>
    
        <select id="selectAgedPeople" resultType="java.lang.String">
            select PB_CNName from ins_family_people where fa_id = #{fa_id}
        </select>
    

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

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

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


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

    相关文章

      网友评论

          本文标题:mybatis --pageHelper分页数据总条数不对和数据

          本文链接:https://www.haomeiwen.com/subject/bupqhltx.html