oracle分页必须配置对应方言,否则pageable生成limit/offset导致ora-00933错误;原生sql需手动指定countquery;动态条件推荐is null模式;大数据量应优先用slice替代page避免count性能瓶颈。
oracle分页必须配对 dialect,否则 pageable 会生成错误 sql
spring data jpa 默认用 h2 或 mysql 的分页语法(limit/offset),oracle 不支持。不配置方言时,jpa 会把 pagerequest.of(1, 10) 翻译成 limit 10 offset 10,直接报 ora-00933:sql 命令未正确结束。
必须在 application.properties 中显式指定 Oracle 方言:
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.Oracle12cDialect
注意:Oracle10gDialect 也行,但推荐用 Oracle12cDialect(支持 OFFSET/FETCH 语法,比老式 ROWNUM 嵌套更清晰);若用的是 Oracle 19c+,可选 Oracle19cDialect。
- 没配 dialect → 报错,且堆栈里看不到分页相关提示,容易误判为 SQL 写错
- 配了
Oracle10gDialect→ Hibernate 会生成三层嵌套ROWNUM查询(兼容性好,但可读性差) - 配了
Oracle12cDialect→ 生成标准OFFSET 10 ROWS FETCH NEXT 10 ROWS ONLY,语义直白,执行计划也更可控
@Query 原生 SQL 分页必须手动写 countQuery
JPQL 查询(如 SELECT u FROM User u)能由 Hibernate 自动推导 count 语句;但一旦用了 nativeQuery = true,框架就无法解析 SQL 结构,Page<t></t> 的 getTotalElements() 就会失效或抛异常。
必须显式提供 countQuery,且它和主查询逻辑要严格一致(WHERE 条件、JOIN、过滤等一个都不能少):
@Query(
value = "SELECT * FROM users WHERE status = :status AND deleted = 0",
countQuery = "SELECT COUNT(*) FROM users WHERE status = :status AND deleted = 0",
nativeQuery = true
)
Page<user> findActiveUsers(@Param("status") String status, Pageable pageable);</user>
- 漏写
countQuery→Page.getTotalElements()返回 0,Page.getTotalPages()永远是 1 -
countQuery里少了个AND deleted = 0→ 总数算多,翻到最后几页会返回空列表但前端仍显示“有下一页” - Oracle 中
COUNT(*)要求 SELECT 列表不能含*外的表达式,所以countQuery必须是纯COUNT(*)形式
动态条件分页别硬拼 SQL,用 :param IS NULL OR field = :param
多个可选字段(比如 cityId、type、minPrice)组合查询时,有人倾向在 service 层 if-else 拼 SQL 字符串,这在 Oracle + Pageable 下极易出错:OFFSET 计算错、COUNT 不同步、参数绑定乱序。
推荐统一走 JPQL + 命名参数,在 Repository 方法里用 IS NULL 模式做条件开关:
@Query("SELECT u FROM User u " +
"WHERE (:cityId IS NULL OR u.cityId = :cityId) " +
" AND (:type IS NULL OR u.type = :type) " +
" AND (:minAge IS NULL OR u.age >= :minAge)")
Page<user> searchUsers(
@Param("cityId") Long cityId,
@Param("type") String type,
@Param("minAge") Integer minAge,
Pageable pageable);</user>
- 所有参数都允许为
null,调用方传null即跳过该条件,无需额外判断 - Hibernate 会把
:cityId IS NULL OR u.cityId = :cityId编译进 WHERE 子句,Oracle 执行计划能走索引(前提是cityId有索引) - 避免用
LIKE '%?%'开头模糊查——Oracle 对前导通配符无法用索引,分页深度越大越慢
大数据量翻页慎用 Page<t></t>,优先考虑 Slice<t></t>
Oracle 执行 COUNT(*) 在千万级表上可能耗时几百毫秒,而用户只关心“能不能加载下一页”,并不需要知道“一共有多少页”。此时 Page<t></t> 的总数统计就成了性能瓶颈。
改用 Slice<t></t> 接口,它只查当前页数据 + 一条额外记录判断是否有下一页,完全跳过 COUNT:
public Slice<user> findRecentUsers(Pageable pageable) {
return userRepository.findByStatus("ACTIVE", pageable);
}</user>
- 返回类型从
Page<t></t>换成Slice<t></t>,方法签名不变,调用方只需改一行类型声明 -
Slice.hasNext()是唯一可靠判断依据;Slice.getTotalElements()永远返回 0,别依赖它 - Oracle 下
Slice生成的 SQL 是OFFSET ... FETCH NEXT (size + 1) ROWS ONLY,多查一条的成本远低于 COUNT
真正难处理的是排序字段无索引、或分页深度极大(如 page=10000)的场景——这时 OFFSET 跳过大量行本身就会变慢,得靠游标分页(cursor-based pagination)替代,Pageable 就不再适用了。











