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></t>),next()从缓存取;缓存空了才发起新查询 - 支持中断(调用
remove()可选,一般不实现;或提供close()清理资源)
示例:基于 OFFSET/LIMIT 的分页迭代器(适用于 MySQL/Oracle 等)
以下是一个轻量级实现,以 User 实体和 JdbcTemplate 为例:
在 Java 中初始化和管理阿里云 SDK客户端。包括单例模式、线程安全、endpoint 与 region 配置、VPC 终端节点、同步与异步等。
public class PagedIterator<t> implements Iterator<list>> {
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;
}
}</t></t></t></t></t></list></t>
使用方式:
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);
}</user></user>
进阶:基于服务端游标(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></t>包装每行 - 应用层:封装成
CursorIterator<t></t>,内部持有未关闭的ResultSet,next()调用rs.next()并映射 - 必须显式
close()(建议实现AutoCloseable),避免连接泄漏 - 不可重复遍历,不支持随机跳页,但内存恒定 O(1)
注意事项与最佳实践
- 避免在
hasNext()中执行完整分页查询(如查全部 100 条再判断是否为空),这会浪费带宽和性能 - 排序字段必须有索引,否则分页(尤其 OFFSET)会全表扫描
- 若业务允许,优先用“基于游标的分页”(cursor-based pagination),即用上一页最后一条记录的 ID/时间戳作为下一页起点,比 OFFSET 更稳定高效
- 结合 Spring Batch 的
ItemReader<t></t>可进一步标准化,它底层就借鉴了类似迭代器的设计 - 线程不安全:该迭代器实例不应被多个线程共享
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










