当前位置: 首页 > news >正文

PageHelper 分页框架查询总数 SQL 错误解决方案:从源码逻辑到版本影响(含实验验证)

目录

一、问题背景与环境信息

二、问题复现(实验一)

2.1 测试代码

XML 映射文件(SQL 语句)

Java 调用代码

2.2 运行报错与异常日志

2.3 核心疑问

三、问题原因分析(基于源码)

3.1 PageHelper 对ORDER BY的处理逻辑

3.2 PageHelper 生成总条数 SQL 的判断逻辑

3.3 最终错误成因

四、解决方案

4.1 核心原理

4.2 修改后的代码(XML)

五、补充实验与版本影响(实验二)

5.1 实验二:布尔类型ORDER BY的特殊情况

测试 SQL:不再使用case when 来进行排序(布尔排序:指定校区优先降序)

不同版本下的结果对比

5.2 版本影响结论

六、总结

一、问题背景与环境信息

本文针对 PageHelper 分页框架在生成查询总数 SQL 时出现的语法错误问题展开分析,涉及工具及版本信息如下:

  • PageHelper 版本:5.1.8(后续补充 6.6.1 版本对比测试)
  • JSqlParser 版本:1.2(PageHelper 5.1.x 默认依赖,一个用于解析 SQL 语句的 Java 库;PageHelper 6.6.1 升级为 4.7 版本)
  • 数据库:PostgreSQL:11.1
  • 核心问题:含特殊ORDER BY子句的查询 SQL,生成总数统计 SQL 时未正确处理,导致报 “字段需 GROUP BY” 错误。

二、问题复现(实验一)

2.1 测试代码

XML 映射文件(SQL 语句)
<select id="selectTest" parameterType="com.jiuaoedu.serviceprofile.pojo.student.StudentDetail" resultMap="BaseResultMap">select *from service_profile.student s-- 按“指定校区优先(0)→其他校区(1)”排序,再按校区ID降序,NULL值后置order by CASE WHEN s.school_area = #{schoolArea} THEN 0 ELSE 1 END,s.school_area DESC NULLS LAST
</select>
Java 调用代码
PageInfo<StudentDetail> pageInfo = PageHelper.startPage(pageNum, pageSize).doSelectPageInfo(() -> studentDetailMapper.selectTest(student)
);

2.2 运行报错与异常日志

  • 生成的错误总数 SQL
    SELECT count(0) FROM service_profile.student s ORDER BY CASE WHEN s.school_area = ? THEN 0 ELSE 1 END, s.school_area DESC NULLS LAST
    
  • 报错原因COUNT(0)聚合查询中包含ORDER BY子句,且school_area未参与GROUP BY,违反 SQL 语法规则。

2.3 核心疑问

  1. 正常情况下 PageHelper 会过滤ORDER BY子句以提升计数性能,为何本次未过滤?
  2. 为何未生成 “外层COUNT嵌套原查询” 的正确 SQL(如下),反而直接将*替换为count(0)

    sql

    SELECT count(0) FROM (select * FROM service_profile.student s ORDER BY ...) tmp_count

三、问题原因分析(基于源码)

PageHelper 分页核心流程分为两步:

1. 查询总条数;

2. 总条数非 0 时执行分页查询。错误根源在于 “生成总条数 SQL” 的逻辑处理。

3.1 PageHelper 对ORDER BY的处理逻辑

核心源码片段(orderByHashParameters方法):

  • 逻辑结论:若ORDER BY子句包含占位符(如实验一中的#{schoolArea}对应?),PageHelper 会保留ORDER BY,不会过滤 —— 这解释了 “疑问 1”。

3.2 PageHelper 生成总条数 SQL 的判断逻辑

核心源码片段(isSimpleCount方法与sqlToCount方法):

/*** 判断是否为“简单查询”,决定是否直接替换查询列为count(0)* @param select 简单查询对象(PlainSelect)* @return 是简单查询返回true,否则false*/
public boolean isSimpleCount(PlainSelect select) {// 1. 含GROUP BY → 非简单查询if (select.getGroupByColumnReferences() != null) {return false;}// 2. 含DISTINCT → 非简单查询if (select.getDistinct() != null) {return false;}// 3. SELECT列含占位符 → 非简单查询for (SelectItem item : select.getSelectItems()) {if (item.toString().contains("?")) {return false;}// 4. SELECT列含聚合函数(非允许列表)→ 非简单查询if (item instanceof SelectExpressionItem) {Expression expression = ((SelectExpressionItem) item).getExpression();if (expression instanceof Function) {// 聚合函数(如SUM、AVG)判断逻辑...}}}return true;
}/*** 将原查询SQL转换为总条数SQL*/
public void sqlToCount(Select select, String name) {SelectBody selectBody = select.getSelectBody();List<SelectItem> COUNT_ITEM = new ArrayList<>();COUNT_ITEM.add(new SelectExpressionItem(new Function("count", new Column(name))));// 若为简单查询,直接替换SELECT列为count(0)if (selectBody instanceof PlainSelect && isSimpleCount((PlainSelect) selectBody)) {((PlainSelect) selectBody).setSelectItems(COUNT_ITEM);} else {// 非简单查询:生成“外层COUNT嵌套原查询”的SQLPlainSelect plainSelect = new PlainSelect();SubSelect subSelect = new SubSelect();subSelect.setSelectBody(selectBody);subSelect.setAlias("tmp_count");plainSelect.setFromItem(subSelect);plainSelect.setSelectItems(COUNT_ITEM);select.setSelectBody(plainSelect);}
}
  • 包含GROUP BY子句:因为GROUP BY会使结果聚合,不再是简单计数

  • 包含DISTINCT关键字:DISTINCT会去重,影响计数结果

  • SELECT列表中包含参数(用?表示):参数可能会导致执行计划不稳定

  • SELECT列表中包含聚合函数(如SUM、AVG等):这些函数会改变计数逻辑

实验截图:

如果该查询是一个简单的查询就将sql的查询列重置为count(0)

  • 逻辑结论:实验一中的原查询满足 “简单查询” 条件(无GROUP BYDISTINCTSELECT列仅为*不含占位符 / 聚合函数),因此 PageHelper 直接将*替换为count(0),未生成嵌套查询 —— 这解释了 “疑问 2”。

3.3 最终错误成因

保留ORDER BY(因含占位符)+ 简单查询直接替换count(0),两者叠加生成了 “COUNT+ORDER BY” 的错误 SQL。

四、解决方案

4.1 核心原理

PageHelper 源码中存在特殊注释标识/*keep orderby*/,若 SQL 中包含该注释,会强制生成 “外层COUNT嵌套原查询” 的 SQL(跳过直接替换逻辑),避免错误。

对应源码片段(getSmartCountSql方法):

public String getSmartCountSql(String sql, String name) {// 若SQL含/*keep orderby*/,直接生成嵌套COUNT查询if (sql.indexOf("/*keep orderby*/") >= 0) {return getSimpleCountSql(sql, name);}// 其他解析逻辑...
}/*** 生成“外层COUNT嵌套原查询”的SQL*/
public String getSimpleCountSql(final String sql, String name) {StringBuilder sb = new StringBuilder(sql.length() + 40);sb.append("select count(").append(name).append(") from (");sb.append(sql);sb.append(") tmp_count");return sb.toString();
}

4.2 修改后的代码(XML)

<select id="selectTest" parameterType="com.jiuaoedu.serviceprofile.pojo.student.StudentDetail" resultMap="BaseResultMap">select *from service_profile.student s/*keep orderby*/ -- 关键注释:强制生成嵌套COUNT查询order by CASE WHEN s.school_area = #{schoolArea} THEN 0 ELSE 1 END,s.school_area DESC NULLS LAST
</select>

实验截图:

  • 该 SQL 符合语法规则,可正常执行计数,分页功能恢复正常。

五、补充实验与版本影响(实验二)

5.1 实验二:布尔类型ORDER BY的特殊情况

测试 SQL:不再使用case when 来进行排序(布尔排序:指定校区优先降序)
<select id="selectTest" parameterType="com.jiuaoedu.serviceprofile.pojo.student.StudentDetail" resultMap="BaseResultMap">select *from service_profile.student sorder by s.school_area = #{schoolArea} desc nulls last
</select>

按照之前的分析结果来看应该会报错,并且生成的sql应该如下

select count(0) from service_profile.student sorder by s.school_area = ? desc nulls last

按照之前的分析结果来看应该会报错,并且生成的sql应该如下

select count(0) from service_profile.student sorder by s.school_area = ? desc nulls last

但是事实却是查询正确,生成的查询总条数sql如下:

select count(0) from (select * from service_profile.student s order by s.school_area = ? desc nulls last) tmp_count 

到这儿我就懵了,不应该如此啊,接着debug。

看到这里我就知道了,PageHelper中引入的jsqlparser较低,jsqlparser解析不了该sql报错,然后就直接返回了simpleCountSql

我升级了pageHelper版本至最新版本6.6.1再次尝试上诉所有内容:

实验结果:

实验1:跟第一次没升级版本出现的报错一样

实验2:第一次没升级不会报错,正常分页查询。在升级版本之后,却出现了报错,原因是因为pageHelper6.6.1中jsqlparser升级为了4.7能够正常解析实验2的结果,然后就出现了和实验1一样的报错。

不同版本下的结果对比
PageHelper 版本JSqlParser 版本执行结果原因分析
5.1.81.2正常计数JSqlParser 1.2 无法解析 “布尔排序” SQL,解析报错后触发降级逻辑,自动生成嵌套 COUNT 查询
6.6.14.7报错(同实验一)JSqlParser 4.7 可正常解析 “布尔排序” SQL,进入 “简单查询 + 保留 ORDER BY” 逻辑,生成错误 SQL

5.2 版本影响结论

  • PageHelper 5.1.8(低版本 JSqlParser):部分复杂ORDER BY因解析失败,可能 “意外正常”,但稳定性差。
  • PageHelper 6.6.1(高版本 JSqlParser):解析能力增强,更多ORDER BY会被保留,需主动添加/*keep orderby*/避免错误。

六、总结

  1. 错误根源:含占位符的ORDER BY被保留 + 简单查询直接替换count(0),导致 SQL 语法错误。
  2. 通用解决方案:在含特殊ORDER BY(含占位符、布尔排序等)的查询 SQL 中,添加/*keep orderby*/注释,强制生成嵌套 COUNT 查询。
  3. 版本建议:升级 PageHelper 后需重点检查ORDER BY相关查询,确保添加该注释,避免因 JSqlParser 解析能力提升导致新错误。
http://www.dtcms.com/a/512291.html

相关文章:

  • NExF——建立3D空间上的曝光场辅助重建
  • 网站建设公司 合肥影视后期制作培训机构全国排名
  • 审计 jenkins获取构建历史,生成excel
  • 【能源与流程工业案例】KBC借助TeeChart 打造工业级数据可视化平台
  • 网站站点的建立wordpress 清单 主题
  • 苏州市建设交易中心网站wordpress 插件 重置密码
  • Linux 网络协议栈数据流跟踪-静态路由demo
  • 【C++】STL容器--list的模拟实现
  • 再次使用xca软件生成自签证书的补充说明
  • 第2章 数据库系统的核心--数据模型
  • 计算机组成基础
  • 迅投xtquant获取股票对应的申万行业
  • 百度seo网站在线诊断小程序开发平台需要网站吗
  • 网站开发用什么书什么网站可以做行测
  • 柳州做网站公司网站建设实施方案
  • excel拆分和合并代码的思路整合和工具打包
  • 信刻——安全生产音视频录音录像自动刻录备份归档管理系统
  • 外贸购物网站开发厦门seo推广外包
  • 公司企业网站设计尺寸discuz 同步wordpress
  • 人工智能(AIT)工具资源的全面概要列表
  • 字符串相乘:从暴力算法到规律优化
  • 突破性技术:DeepSeek-OCR通过光学压缩解决大语言模型长上下文挑战
  • wap的网站模板百度运营优化师
  • 网站项目框架1688货源网一件代销
  • 小白python入门 - 4. Python 中的变量:从基础概念到编程思维的构建
  • 日语学习-日语知识点小记-构建基础-JLPT-N3阶段-二阶段(5):文法運用
  • 表格识别技术:将纸质信息转化为可分析的结构化数据,推动智能办公革命
  • 读懂 YOLOv4:兼顾速度与精度的目标检测王者
  • 磁悬浮轴承控制方法全景解析:从经典策略到智能前沿
  • 响应式网站无法做百度联盟wordpress无中断音乐插件