详细介绍:PageHelper 分页框架查询总数 SQL 错误解决方案:从源码逻辑到版本影响(含实验验证)
2025-12-12 10:37 tlnshuju 阅读(0) 评论(0) 收藏 举报目录
3.2 PageHelper 生成总条数 SQL 的判断逻辑
测试 SQL:不再使用case when 来进行排序(布尔排序:指定校区优先降序)
一、问题背景与环境信息
本文针对 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 语句)
Java 调用代码
PageInfo 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 核心疑问
- 正常情况下 PageHelper 会过滤
ORDER BY子句以提升计数性能,为何本次未过滤? - 为何未生成 “外层
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 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嵌套原查询”的SQL
PlainSelect 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 BY、DISTINCT,SELECT列仅为*不含占位符 / 聚合函数),因此 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)
实验截图:


- 该 SQL 符合语法规则,可正常执行计数,分页功能恢复正常。
五、补充实验与版本影响(实验二)
5.1 实验二:布尔类型ORDER BY的特殊情况
测试 SQL:不再使用case when 来进行排序(布尔排序:指定校区优先降序)
按照之前的分析结果来看应该会报错,并且生成的sql应该如下
select count(0) from service_profile.student s
order by s.school_area = ? desc nulls last
按照之前的分析结果来看应该会报错,并且生成的sql应该如下
select count(0) from service_profile.student s
order 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.8 | 1.2 | 正常计数 | JSqlParser 1.2 无法解析 “布尔排序” SQL,解析报错后触发降级逻辑,自动生成嵌套 COUNT 查询 |
| 6.6.1 | 4.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*/避免错误。
六、总结
- 错误根源:含占位符的
ORDER BY被保留 + 简单查询直接替换count(0),导致 SQL 语法错误。 - 通用解决方案:在含特殊
ORDER BY(含占位符、布尔排序等)的查询 SQL 中,添加/*keep orderby*/注释,强制生成嵌套 COUNT 查询。 - 版本建议:升级 PageHelper 后需重点检查
ORDER BY相关查询,确保添加该注释,避免因 JSqlParser 解析能力提升导致新错误。
浙公网安备 33010602011771号