MySQL分页应由数据库通过LIMIT ? OFFSET ?实现,而非Java层用ResultSet.absolute跳转;映射字段需显式别名或按位置取值;须校验分页参数防SQL错误与性能问题。

MySQL分页用LIMIT但Java里ResultSet不支持跳转
直接用 ResultSet.absolute(pageSize * (pageNum - 1) + 1) 在大多数驱动(比如 MySQL Connector/J 8.0+)上会抛 SQLFeatureNotSupportedException,因为默认的 Statement 是 forward-only 类型。这不是你代码写错了,是 JDBC 的默认行为限制。
正确做法是让数据库做分页,而不是在 Java 层遍历跳过记录:
- 用
PreparedStatement拼LIMIT ? OFFSET ?,别手拼 SQL 字符串防注入 - 确保
Connection没设setHoldability(ResultSet.HOLD_CURSORS_OVER_COMMIT)这类干扰项 - 如果用 HikariCP 等连接池,确认没开启
leakDetectionThreshold导致 ResultSet 提前关闭
ResultSet映射到Contact对象时字段名对不上
常见现象是所有字段都是 null 或部分为 null,尤其当 SQL 用了别名、函数(如 CONCAT(last_name, ', ', first_name))或大小写混用时。JDBC 不保证列名返回和 SELECT 子句完全一致,尤其在 MySQL 默认配置下。
安全映射方式只有两种:
立即学习“Java免费学习笔记(深入)”;
- 用
rs.getString(1),rs.getString(2)等按位置取值——简单但脆弱,SQL 字段顺序一变就崩 - 用
rs.findColumn("contact_id")先查列索引再取值,或者统一在 SQL 里显式写别名:SELECT id AS contact_id, name AS full_name FROM contacts - 避免依赖
rs.getMetaData().getColumnName(i),它在某些驱动版本里返回的是原始表字段名而非别名
分页参数校验不到位导致SQL报错或越界
LIMIT -5 OFFSET 10 或 LIMIT 10 OFFSET -1 会直接触发 MySQL 报错 ERROR 1064 (42000);更隐蔽的是传入超大 OFFSET(比如几百万),MySQL 会全表扫描跳过前面所有行,性能断崖下跌。
必须在 Java 层拦截非法值:
pageNum 直接拒绝,返回空列表或抛 <code>IllegalArgumentException- 设置硬上限,例如
if (offset > 100000) throw new IllegalArgumentException("OFFSET too large") - 用
SELECT COUNT(*)配合分页查询,但别在每次请求都查总数——可缓存总条数,或改用“下一页是否存在”逻辑(查 pageSize+1 条,只显示前 pageSize 条)
Spring JdbcTemplate没自动处理LIMIT方言差异
如果你用的是 JdbcTemplate 而不是 MyBatis 或 JPA,它本身不解析或重写 SQL 中的 LIMIT。也就是说,写死 LIMIT ?,? 在 H2 测试库会报错,因为 H2 用 TOP 或 LIMIT .. OFFSET 语法取决于版本。
实际项目中要收敛分页逻辑:
- 别在 DAO 方法里写裸 SQL 分页,封装成
PageRequest+ 自定义Pageable解析器 - 若坚持手写,把 LIMIT 拼装抽到工具类,按
dataSource.getUrl()包含的数据库类型(mysql,postgresql,h2)分支处理 - 测试阶段务必连真实数据库跑一次分页查询,H2 的兼容模式(
DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE;MODE=MySQL)只能覆盖一部分行为
分页不是加个 LIMIT 就完事,数据库执行计划、驱动实现细节、连接池生命周期,三者只要有一环没对齐,结果就可能静默错位或突然报错。最保险的做法,是让分页逻辑彻底脱离 ResultSet 的游标操作,全部下沉到 SQL 层,并且每个环境都实测验证。










