链接个人博客踩坑实录PageHelper 用户服务团队筛选致总条数异常原理 实操方案昨天遇到一个bugPageHelper 是 MyBatis 生态下的常用分页工具但结合 “用户权限筛选”如按服务团队过滤数据时很容易出现总条数异常。最近项目中就遇到用PageHelper.startPage分页后根据 “当前登录用户的服务团队” 筛选数据最终返回的总条数等于每页条数rows而非符合团队权限的真实总数。今天结合真实业务代码彻底讲清这个问题。问题原因 重点PageHelper 在执行查询后会返回一个 Page 对象继承自 ArrayList 其中包含分页信息total、pages 等。原代码在分页后对 List 进行过滤时创建了新的 ArrayList 导致丢失了 Page 对象的分页信息使得前端收到的 total 等于当前页的数据量。一、回顾PageHelper 分页的核心原理要解决问题先明确 PageHelper 的工作逻辑核心依赖 MyBatis 拦截器存参数PageHelper.startPage(pageNum, pageSize)将分页参数存入 ThreadLocal避免多线程干扰拦 SQL拦截 MyBatis 的Executor.query()方法检测到分页参数时改造 SQL双查询先执行 count 查询获总条数 total再执行带分页语法的 SQL获当前页 list封结果将 total 和 list 封装成com.github.pagehelper.Page对象继承 ArrayList清除 ThreadLocal 参数。关键结论PageHelper 的 total 仅来自 “第一次 SQL 查询”后续内存筛选不会同步更新 total—— 这是本次异常的核心根源。二、问题复现用户服务筛选后总条数异常以下是项目中真实的 “分页 权限筛选” 代码也是导致异常的典型写法1. 错误代码示例// 1. 分页参数第1页每页20条rows20 PageHelper.startPage(1, 20); // 2. 查询待恢复数据列表 ListRecoverBo list recoverMapper.queryRecoverList(DispatchBo); // 此时list实际是PageRecoverBo对象total为全量数据总数如100条 // 3. 核心逻辑获取当前登录用户的服务按筛选数据 // 3.1 查当前登录用户所属服务 String userServeTeam RightsManageMapper.queryUserServe(user.getUserId()); // 3.2 团队不为空且有调度参数时筛选数据并行流提高效率 if (userServe ! null DispatchBo.getSERVE() ! null) { list list.parallelStream() .filter(recoverBo - // 筛选规则待恢复数据的服务团队为空或在用户服务团队内 recoverBo.getSERVE() null || (recoverBo.getSERVE() ! null userServe.contains(recoverBo.getSERVE())) ) .collect(Collectors.toList()); // 假设筛选后剩20条刚好等于rows } // 4. 封装分页结果返回 PageInfoRecoverBo pageInfo new PageInfo(list); // 异常结果total20预期是符合团队权限的真实总数如60条 System.out.println(分页总条数 pageInfo.getTotal());2. 异常原因深度解析为什么pageInfo.getTotal()会等于 20rows关键在PageInfo的构造逻辑与代码中的数据类型变化步骤 2 中list是PageRecoverBo对象包含正确的 total100 条步骤 3 中通过parallelStream筛选后collect(Collectors.toList())返回的是普通 ArrayList不再是 Page 对象步骤 4 中PageInfo接收普通 ArrayList 时会默认将list.size()当作 total—— 因筛选后 list.size ()20故 total20与真实团队权限下的总数60 条严重不符。3. 本质问题我们将 “分页后的局部权限筛选数据” 误作为 “分页的全局数据”导致PageInfo丢失了 Page 对象中的 total 元数据 ——团队筛选在内存中执行未参与 PageHelper 的 count 查询计算。三、解决方案结合团队筛选的正确实现核心思路让 “用户服务团队筛选条件” 参与 PageHelper 的分页计算分两种场景处理。场景 1团队筛选可通过 SQL 实现推荐性能更优若用户服务团队如 “A, B”可通过 SQL 条件表达优先在 Mapper 层加入筛选参数让 PageHelper 直接对 “符合权限的数据” 分页total 会自动计算正确。正确代码示例// 1. 改造Mapper接口加入用户服务参数 List\RecoverBo queryRecoverListByServe( #x20; Param(llDispatchBo) LlDispatchBo llDispatchBo, #x20; Param(userServe) String userServe // 新增当前用户服务 ); // 2. 改造MyBatis XMLSQL层面加入团队筛选条件 select idqueryRecoverListByServe resultTypecom.example.entity.RecoverBo select * from recover_table where !-- 原有llDispatchBo相关条件 -- if testllDispatchBo.SERVE ! null and SERVE #{llDispatchBo.SERVE} /if !-- 新增权限筛选 -- if testuserServe ! null and (SERVETEAM is null or SERVETEAM in#x20; \!-- 处理用户服务逗号分隔转列表 -- ; \foreach collectionuserServe.split(,) itemteam open( close) separator, \#{team} \/foreach ) ; \/if \/where \/select // 3. 业务层代码分页SQL级筛选 // 3.1 获取当前登录用户服务 String userServe RightsManageMapper.queryUserServe(user.getUserId()); // 3.2 分页参数设置 PageHelper.startPage(1, 20); // 3.3 调用带筛选的Mapper方法PageHelper自动计算正确total ListRecoverBo list recoverMapper.queryRecoverListByServe(DispatchBo, userServe); // 此时list仍是PageRecoverBo对象total为符合团队权限的总数如60条 // 4. 封装PageInfo返回无需额外处理 PageInfoRecoverBo pageInfo new PageInfo(list); System.out.println(分页总条数 pageInfo.getTotal()); // 输出60正确优势筛选逻辑在数据库层执行减少无效数据传输PageHelper 自动同步 total无内存操作风险。场景 2必须在内存中筛选特殊场景如多数据源联动若用户服务团队筛选依赖复杂逻辑如跨系统校验需在内存中处理需手动保留分页元数据并同步 total。正确代码示例// 1. 分页查询全量数据强转为Page对象保留元数据 PageHelper.startPage(1, 20); PageRecoverBo recoverPage (PageRecoverBo) recoverMapper.queryRecoverList(DispatchBo); // 此时recoverPage.getTotal()是全量数据总数如100条暂用不到 // 2. 获取当前登录用户服务 String userServe claimRightsManageMapper.queryUserServe(user.getUserId()); // 3. 内存筛选保留原筛选逻辑 ListRecoverBo filteredList new ArrayList(); if (userServe ! null llDispatchBo.getSERVE() ! null) { filteredList recoverPage.getResult().parallelStream() .filter(recoverBo - recoverBo.getSERVETEAM() null ||#x20; (recoverBo.getSERVETEAM() ! null userServe.contains(recoverBo.getSERVE())) ) .collect(Collectors.toList()); } // 4. 关键手动查询符合权限的总条数需新增Mapper方法 long filteredTotal recoverMapper.queryRecoverTotalByServe(DispatchBo, userServe); // 注queryRecoverTotalByServe逻辑与SQL筛选一致仅返回count(0) // 5. 构造新的Page对象同步正确的元数据 PageRecoverBo resultPage new Page( recoverPage.getPageNum(), // 原页码1 recoverPage.getPageSize(), // 原每页条数20 filteredTotal // 筛选后的真实总条数如60 ); resultPage.setResult(filteredList); // 设置筛选后的当前页数据 // 6. 封装PageInfo返回 PageInfoRecoverBo pageInfo new PageInfo(resultPage); System.out.println(分页总条数 pageInfo.getTotal()); // 输出60正确关键说明queryRecoverTotalByServe是新增的计数方法需在 SQL 中实现与内存筛选一致的团队权限逻辑确保filteredTotal真实可靠。四、避坑指南结合团队筛选的 3 个关键注意点优先 SQL 筛选用户服务团队、权限等固定筛选条件尽量通过 SQL 实现如in、like避免内存筛选导致的 total 异常保留 Page 元数据PageHelper.startPage后第一次查询的结果需强转为Page对象避免直接用List接收导致元数据丢失内存筛选必补 total若必须内存筛选需单独写计数方法同步真实 total不可依赖list.size()填充 total。五、总结PageHelper 总条数异常的本质仍是 “筛选逻辑未同步参与分页计算”。无论是 SQL 层筛选推荐还是内存层筛选手动补 total核心都是让 “权限条件” 与 “PageHelper 的 count 查询” 联动。
踩坑实录:PageHelper 分页后筛选数据致总条数异常?原理 + 解决方案
链接个人博客踩坑实录PageHelper 用户服务团队筛选致总条数异常原理 实操方案昨天遇到一个bugPageHelper 是 MyBatis 生态下的常用分页工具但结合 “用户权限筛选”如按服务团队过滤数据时很容易出现总条数异常。最近项目中就遇到用PageHelper.startPage分页后根据 “当前登录用户的服务团队” 筛选数据最终返回的总条数等于每页条数rows而非符合团队权限的真实总数。今天结合真实业务代码彻底讲清这个问题。问题原因 重点PageHelper 在执行查询后会返回一个 Page 对象继承自 ArrayList 其中包含分页信息total、pages 等。原代码在分页后对 List 进行过滤时创建了新的 ArrayList 导致丢失了 Page 对象的分页信息使得前端收到的 total 等于当前页的数据量。一、回顾PageHelper 分页的核心原理要解决问题先明确 PageHelper 的工作逻辑核心依赖 MyBatis 拦截器存参数PageHelper.startPage(pageNum, pageSize)将分页参数存入 ThreadLocal避免多线程干扰拦 SQL拦截 MyBatis 的Executor.query()方法检测到分页参数时改造 SQL双查询先执行 count 查询获总条数 total再执行带分页语法的 SQL获当前页 list封结果将 total 和 list 封装成com.github.pagehelper.Page对象继承 ArrayList清除 ThreadLocal 参数。关键结论PageHelper 的 total 仅来自 “第一次 SQL 查询”后续内存筛选不会同步更新 total—— 这是本次异常的核心根源。二、问题复现用户服务筛选后总条数异常以下是项目中真实的 “分页 权限筛选” 代码也是导致异常的典型写法1. 错误代码示例// 1. 分页参数第1页每页20条rows20 PageHelper.startPage(1, 20); // 2. 查询待恢复数据列表 ListRecoverBo list recoverMapper.queryRecoverList(DispatchBo); // 此时list实际是PageRecoverBo对象total为全量数据总数如100条 // 3. 核心逻辑获取当前登录用户的服务按筛选数据 // 3.1 查当前登录用户所属服务 String userServeTeam RightsManageMapper.queryUserServe(user.getUserId()); // 3.2 团队不为空且有调度参数时筛选数据并行流提高效率 if (userServe ! null DispatchBo.getSERVE() ! null) { list list.parallelStream() .filter(recoverBo - // 筛选规则待恢复数据的服务团队为空或在用户服务团队内 recoverBo.getSERVE() null || (recoverBo.getSERVE() ! null userServe.contains(recoverBo.getSERVE())) ) .collect(Collectors.toList()); // 假设筛选后剩20条刚好等于rows } // 4. 封装分页结果返回 PageInfoRecoverBo pageInfo new PageInfo(list); // 异常结果total20预期是符合团队权限的真实总数如60条 System.out.println(分页总条数 pageInfo.getTotal());2. 异常原因深度解析为什么pageInfo.getTotal()会等于 20rows关键在PageInfo的构造逻辑与代码中的数据类型变化步骤 2 中list是PageRecoverBo对象包含正确的 total100 条步骤 3 中通过parallelStream筛选后collect(Collectors.toList())返回的是普通 ArrayList不再是 Page 对象步骤 4 中PageInfo接收普通 ArrayList 时会默认将list.size()当作 total—— 因筛选后 list.size ()20故 total20与真实团队权限下的总数60 条严重不符。3. 本质问题我们将 “分页后的局部权限筛选数据” 误作为 “分页的全局数据”导致PageInfo丢失了 Page 对象中的 total 元数据 ——团队筛选在内存中执行未参与 PageHelper 的 count 查询计算。三、解决方案结合团队筛选的正确实现核心思路让 “用户服务团队筛选条件” 参与 PageHelper 的分页计算分两种场景处理。场景 1团队筛选可通过 SQL 实现推荐性能更优若用户服务团队如 “A, B”可通过 SQL 条件表达优先在 Mapper 层加入筛选参数让 PageHelper 直接对 “符合权限的数据” 分页total 会自动计算正确。正确代码示例// 1. 改造Mapper接口加入用户服务参数 List\RecoverBo queryRecoverListByServe( #x20; Param(llDispatchBo) LlDispatchBo llDispatchBo, #x20; Param(userServe) String userServe // 新增当前用户服务 ); // 2. 改造MyBatis XMLSQL层面加入团队筛选条件 select idqueryRecoverListByServe resultTypecom.example.entity.RecoverBo select * from recover_table where !-- 原有llDispatchBo相关条件 -- if testllDispatchBo.SERVE ! null and SERVE #{llDispatchBo.SERVE} /if !-- 新增权限筛选 -- if testuserServe ! null and (SERVETEAM is null or SERVETEAM in#x20; \!-- 处理用户服务逗号分隔转列表 -- ; \foreach collectionuserServe.split(,) itemteam open( close) separator, \#{team} \/foreach ) ; \/if \/where \/select // 3. 业务层代码分页SQL级筛选 // 3.1 获取当前登录用户服务 String userServe RightsManageMapper.queryUserServe(user.getUserId()); // 3.2 分页参数设置 PageHelper.startPage(1, 20); // 3.3 调用带筛选的Mapper方法PageHelper自动计算正确total ListRecoverBo list recoverMapper.queryRecoverListByServe(DispatchBo, userServe); // 此时list仍是PageRecoverBo对象total为符合团队权限的总数如60条 // 4. 封装PageInfo返回无需额外处理 PageInfoRecoverBo pageInfo new PageInfo(list); System.out.println(分页总条数 pageInfo.getTotal()); // 输出60正确优势筛选逻辑在数据库层执行减少无效数据传输PageHelper 自动同步 total无内存操作风险。场景 2必须在内存中筛选特殊场景如多数据源联动若用户服务团队筛选依赖复杂逻辑如跨系统校验需在内存中处理需手动保留分页元数据并同步 total。正确代码示例// 1. 分页查询全量数据强转为Page对象保留元数据 PageHelper.startPage(1, 20); PageRecoverBo recoverPage (PageRecoverBo) recoverMapper.queryRecoverList(DispatchBo); // 此时recoverPage.getTotal()是全量数据总数如100条暂用不到 // 2. 获取当前登录用户服务 String userServe claimRightsManageMapper.queryUserServe(user.getUserId()); // 3. 内存筛选保留原筛选逻辑 ListRecoverBo filteredList new ArrayList(); if (userServe ! null llDispatchBo.getSERVE() ! null) { filteredList recoverPage.getResult().parallelStream() .filter(recoverBo - recoverBo.getSERVETEAM() null ||#x20; (recoverBo.getSERVETEAM() ! null userServe.contains(recoverBo.getSERVE())) ) .collect(Collectors.toList()); } // 4. 关键手动查询符合权限的总条数需新增Mapper方法 long filteredTotal recoverMapper.queryRecoverTotalByServe(DispatchBo, userServe); // 注queryRecoverTotalByServe逻辑与SQL筛选一致仅返回count(0) // 5. 构造新的Page对象同步正确的元数据 PageRecoverBo resultPage new Page( recoverPage.getPageNum(), // 原页码1 recoverPage.getPageSize(), // 原每页条数20 filteredTotal // 筛选后的真实总条数如60 ); resultPage.setResult(filteredList); // 设置筛选后的当前页数据 // 6. 封装PageInfo返回 PageInfoRecoverBo pageInfo new PageInfo(resultPage); System.out.println(分页总条数 pageInfo.getTotal()); // 输出60正确关键说明queryRecoverTotalByServe是新增的计数方法需在 SQL 中实现与内存筛选一致的团队权限逻辑确保filteredTotal真实可靠。四、避坑指南结合团队筛选的 3 个关键注意点优先 SQL 筛选用户服务团队、权限等固定筛选条件尽量通过 SQL 实现如in、like避免内存筛选导致的 total 异常保留 Page 元数据PageHelper.startPage后第一次查询的结果需强转为Page对象避免直接用List接收导致元数据丢失内存筛选必补 total若必须内存筛选需单独写计数方法同步真实 total不可依赖list.size()填充 total。五、总结PageHelper 总条数异常的本质仍是 “筛选逻辑未同步参与分页计算”。无论是 SQL 层筛选推荐还是内存层筛选手动补 total核心都是让 “权限条件” 与 “PageHelper 的 count 查询” 联动。