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<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代码内完成封装。这种方式很容易理解,就不罗列代码了。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,423评论 6 491
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,147评论 2 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,019评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,443评论 1 283
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,535评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,798评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,941评论 3 407
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,704评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,152评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,494评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,629评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,295评论 4 329
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,901评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,742评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,978评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,333评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,499评论 2 348

推荐阅读更多精彩内容