jOOQ 按第二个嵌套 record 排序,渲染 SQL 就数组越界
MySQL等方言会展开嵌套record投影,ORDER BY序号的重映射可能读错索引。保留排序语义,显式列出实际排序字段,并检查无异常时是否漏了排序列。
还没把 SQL 发给数据库,jOOQ 就在渲染查询时抛出 ArrayIndexOutOfBoundsException。如果投影包含多个嵌套 row(...),而 ORDER BY 使用投影序号,应先检查嵌套列展开后的序号映射。
例如,按第二个嵌套 record 排序的形状是:
DSL.using(SQLDialect.MYSQL).render(
DSL.select(
DSL.row(DSL.inline(1), DSL.inline(2)),
DSL.row(DSL.inline(3), DSL.inline(4), DSL.inline(5))
).orderBy(DSL.inline(2))
);
这里的 2 指原投影的第二项,它包含三个成员。MySQL 这类不采用原生嵌套 record 投影的方言,会把它们展开成实际列;第二项的排序应覆盖展开后的第3、4、5列。
不报异常也可能排错
受影响的 SelectQueryImpl.toSQLOrderBy() 先根据原投影索引取得展开位置,随后却在需要多个排序列时,用展开位置去索引原投影尺寸数组。两个下标含义不同,可能越界,也可能刚好落在有效位置,却取到错误的成员数量。
因此,另一种表现是查询成功执行,但预期的 ORDER BY 3, 4 只生成 ORDER BY 3。后续数据看起来偶尔顺序正确,也不能说明排序完整。
3.22开发快照有这项明确报告,正式3.21.9的对应分支仍使用同样的下标。先核对实际方言与渲染 SQL;普通平面投影或按明确字段排序的查询,不应只因出现相似异常就套用这个结论。
在查询中明确实际排序字段
临时处理可以保留原嵌套返回结构,把按 record 序号排序改成按其实际成员逐项排序。若业务本来要按第二个 record 的三个字段排序,就按同一顺序写出三个字段表达式,保留原升降序和 NULL 排序要求。
不要直接把 2 改成 3 来碰运气。前者是原投影中的第二项,后者在另一个阶段可能是展开列序号,两者不是稳定的替代关系。也不要为了绕开异常删除整个排序;分页或取首行时,这会改变返回哪批数据。
若字段来自关联表、计算表达式或别名,沿用项目的 DSL 字段引用,避免将来自请求的字段名直接拼接为 SQL。视图和数据库方言不同,别名的可见范围也需要核对。
比较完整排序键
在隔离环境准备首排序字段相同、后续字段不同的数据,检查渲染 SQL 的全部排序项,再比较结果顺序。分页查询还要核对跨页重复和遗漏,不能只测试构造函数不再抛异常。
截至2026年10月25日核对的正式tag仍到3.21.9,后续修复目标包括3.21.10。采用升级方案时,要确认已发布依赖真实包含这项更正;未发布的维护分支不能直接算当前可用修复。


