Java Iterator 本身不支持数据库游标分页,但可借鉴其懒加载、单向移动思想封装自定义分页迭代器,通过状态管理(offset/ cursor)、缓存批次数据、按需查询实现内存友好、可中断的分页遍历。

Java 中的 Iterator 本身不直接支持数据库游标分页,但它提供了一种抽象遍历行为的范式。在批处理分页查询中,我们不是用原生 Iterator 包装 ResultSet,而是借鉴其“一次只取一个、懒加载、单向移动”的思想,封装一个自定义迭代器来管理游标(如 MySQL 的 OFFSET/LIMIT、PostgreSQL 的 cursor、或 MyBatis 的 RowBounds),实现内存友好、可中断、可复用的分页遍历逻辑。
核心思路:用 Iterator 封装分页请求的“下一页”动作
把每次分页查询(如查第 n 页 100 条)看作一次“next()”调用,将页码/游标位置、查询参数、当前批次数据缓存作为状态封装在迭代器内部。外部只需 while(it.hasNext()) { it.next() },无需感知 SQL 或分页策略。
- 迭代器内部维护当前偏移量(
offset)或游标值(cursorId),每次next()触发一次 DB 查询 -
hasNext()不预查全量,而是尝试查下一页第一条(如LIMIT 1),判断是否存在 - 查询结果缓存在迭代器内(如
List<T>),next()从缓存取;缓存空了才发起新查询 - 支持中断(调用
remove()可选,一般不实现;或提供close()清理资源)
示例:基于 OFFSET/LIMIT 的分页迭代器(适用于 MySQL/Oracle 等)
以下是一个轻量级实现,以 User 实体和 JdbcTemplate 为例:
Java项目代码review工具。分析Git变更+完整调用链路上下文,推断业务需求,进行多维度评分和分类汇总,生成完整PRD文档。包含细粒度Java代码审查清单(Null安全、异常处理、Streams、并发、equals/hashCode、资源管理、API设计、性能、MyBatis/ORM、事务边界、SQL/DD...
public class PagedIterator<T> implements Iterator<List<T>> {
private final JdbcTemplate jdbcTemplate;
private final String sql;
private final RowMapper<T> rowMapper;
private final int pageSize;
private int offset = 0;
private List<T> currentBatch = Collections.emptyList();
public PagedIterator(JdbcTemplate jdbcTemplate, String sql, RowMapper<T> rowMapper, int pageSize) {
this.jdbcTemplate = jdbcTemplate;
this.sql = sql;
this.rowMapper = rowMapper;
this.pageSize = pageSize;
}
@Override
public boolean hasNext() {
if (!currentBatch.isEmpty()) return true;
// 查下一页是否存在(仅查 1 条)
String checkSql = "SELECT 1 FROM (" + sql + ") t LIMIT 1 OFFSET ?";
List<?> exists = jdbcTemplate.query(checkSql, ps -> ps.setInt(1, offset), (rs, i) -> 1);
return !exists.isEmpty();
}
@Override
public List<T> next() {
if (currentBatch.isEmpty()) {
String pageSql = sql + " LIMIT ? OFFSET ?";
currentBatch = jdbcTemplate.query(pageSql,
ps -> {
ps.setInt(1, pageSize);
ps.setInt(2, offset);
},
rowMapper);
offset += pageSize;
}
List<T> result = currentBatch;
currentBatch = Collections.emptyList();
return result;
}
}
使用方式:
立即学习“Java免费学习笔记(深入)”;
String sql = "SELECT id, name FROM user WHERE status = ?";
PagedIterator<User> it = new PagedIterator<>(
jdbcTemplate,
sql,
(rs, i) -> new User(rs.getLong("id"), rs.getString("name")),
100
);
while (it.hasNext()) {
List<User> batch = it.next(); // 每次返回一页数据
processBatch(batch);
}
进阶:基于服务端游标(Server-Side Cursor)的真正流式迭代器
对于超大数据集(如千万级),OFFSET 会变慢。此时应切换为数据库原生游标(如 PostgreSQL 的 DECLARE / FETCH,MySQL 8.0+ 的 WITH CURSOR,或 JDBC 的 setFetchSize(Integer.MIN_VALUE) 启用流式读取)。
- JDBC 层:用
Statement.setFetchSize(Integer.MIN_VALUE)+ResultSet迭代,配合Iterator<T>包装每行 - 应用层:封装成
CursorIterator<T>,内部持有未关闭的ResultSet,next()调用rs.next()并映射 - 必须显式
close()(建议实现AutoCloseable),避免连接泄漏 - 不可重复遍历,不支持随机跳页,但内存恒定 O(1)
注意事项与最佳实践
- 避免在
hasNext()中执行完整分页查询(如查全部 100 条再判断是否为空),这会浪费带宽和性能 - 排序字段必须有索引,否则分页(尤其 OFFSET)会全表扫描
- 若业务允许,优先用“基于游标的分页”(cursor-based pagination),即用上一页最后一条记录的 ID/时间戳作为下一页起点,比 OFFSET 更稳定高效
- 结合 Spring Batch 的
ItemReader<T>可进一步标准化,它底层就借鉴了类似迭代器的设计 - 线程不安全:该迭代器实例不应被多个线程共享

















