DEBUG.WIKI
IPPeak:从目标地区访问网页,住宅代理、SEO 监测、广告验证
RackNerd;$21.99/年 · 1GB KVM
Evoxt;最高 6.0GHz CPU;高频云服务器 · 免费每周备份;查看配置 →

Spring Data 分页报 JPQL 语法错误,检查 DISTINCT 计数表达式

DISTINCT 与函数、CASE 或构造器组合时,旧计数转换可能丢掉空格和括号。核对自动 count 与正式 4.0.8 修正;显式 countQuery 必须保持同样的筛选和去重单位。

Repository 的分页查询本身能取数据,执行总数查询时却报 BadJpqlGrammarException: no viable alternative。如果 SELECT 同时包含 DISTINCT 和 coalesce、CASE、CAST 或构造器里的函数,要查看 Spring Data 自动生成的 count 查询。

受影响的转换会丢掉多 token 表达式中的空格,在不该删除的位置移除 AS,或者在展开构造器时一起去掉内部函数括号。于是合法的原查询被改成无法解析的计数表达式。

例如,下面是假定 User 有 name、lastname 字符串属性和 foo 筛选属性的查询:

select distinct coalesce(u.name, u.lastname)
from User u
where u.foo = ?1

诊断重点是对应的 count 是否仍保留完整 coalesce(...),而不是继续修改本来正确的实体属性。底层数据库能单独执行某段 SQL,也不能证明中间生成的 JPQL 是合法的。

采用保留表达式结构的维护版本

正式 Spring Data JPA 4.0.8 已包含相关计数转换调整。其正式源码和回归用例保留了 DISTINCT 函数表达式的括号与 CASE 的 token 边界,不再沿用旧的扁平化处理。

仍使用相关旧实现的工程,应在框架兼容的依赖管理入口评估包含修正的版本。若由 Spring Boot 管理,不要只给一个 Spring Data JPA JAR 单独加最新版本,忽略 Commons、Hibernate 和 Jakarta API 的配套要求。

这个修正针对计数查询的构造,不保证任意 JPQL 方言都接受所有函数或多列 DISTINCT 计数。保留实际 JPA provider 与查询语言,核对最终生成的 count,以及 provider 接着生成的 SQL。

显式 countQuery 必须计算同一件事

暂时不能更新时,可以给 @Query 提供明确的 countQuery,让分页不再依赖错误的自动推导。下面只演示分页对象为实体本身时的对应结构:

@Query(
    value = "select distinct u from User u where u.foo = ?1",
    countQuery = "select count(distinct u.id) from User u where u.foo = ?1"
)
Page<User> findUsers(String foo, Pageable pageable);

示例方法放在已有 Repository 中,需要 Spring Data 的 Query、Page、Pageable,并假定 User 的主键是 id、foo 是字符串。实际项目必须保留租户、权限、连接条件和参数;这段例子不能直接替代前面“按计算后的字符串去重”的查询。

两个不同用户可能得到同一个 coalesce(name, lastname)。统计用户 ID 会得到用户数量,统计去重后的字符串才是那种投影的总数,二者不能因为都能运行就互换。空值、JOIN 引起的重复和多列投影也要一起考虑;复杂计数无法可靠表达时,应在自定义查询实现中分别提供内容与正确总数,而不是返回一个凑出来的 count。

用能真正触发 count 的分页数据复查

分页结果较少时,框架可能从内容数量判断总数,从而不执行 count。只验证空库或不足一页的数据,容易错过故障路径。

在受控数据中覆盖超过一页、重复投影、空值和筛选后无结果,确认查询执行、总元素数和总页数都符合业务。若分页内容正确而总数异常,继续核对显式 count 与主查询的筛选和去重单位,不通过移除 DISTINCT 或改成不计数的返回类型来悄悄改变接口语义。