
本文介绍在不使用 Spring Batch 的前提下,利用 Spring Data JPA 的分页能力(PagingAndSortingRepository)实现百万级数据的流式分批读取与增量文件写入,避免内存溢出和 OOM 风险。
本文介绍在不使用 spring batch 的前提下,利用 spring data jpa 的分页能力(`pagingandsortingrepository`)实现百万级数据的流式分批读取与增量文件写入,避免内存溢出和 oom 风险。
当需要处理超大规模数据(如 100 万+ 条记录)并导出至文件时,一次性加载全部数据到内存(如 dataRepository.findAll())极易引发 OutOfMemoryError,且严重降低系统响应性。理想方案是采用分页拉取 + 流式写入策略:每次仅加载固定批次(如 10 万条),立即写入文件并释放内存,从而实现低内存占用、高可控性的批量导出。
✅ 推荐实现方式:基于 Pageable 的分页迭代
Spring Data JPA 的 PagingAndSortingRepository 提供了 findAll(Pageable) 方法,返回 Page
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Slice;
import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
public void exportDataToFile(String path) throws IOException {
try (FileWriter fw = new FileWriter(path);
BufferedWriter bw = new BufferedWriter(fw)) {
PageRequest pageRequest = PageRequest.of(0, 100_000); // 每页 10 万条
Slice<data> slice;
do {
slice = dataRepository.findAll(pageRequest);
for (Data data : slice.getContent()) {
bw.write(data.toString());
bw.newLine(); // 建议显式换行,确保格式清晰
}
bw.flush(); // 强制刷新缓冲区,确保数据及时落盘
pageRequest = pageRequest.next();
} while (slice.hasNext());
}
}</data>
⚠️ 关键注意事项
- 务必使用 try-with-resources:确保 BufferedWriter 和 FileWriter 在异常或正常结束时自动关闭,防止文件句柄泄漏。
-
优先选用 Slice
而非 Page :Slice 不执行 COUNT(*) 查询,性能更高;若无需总记录数,可显著提升分页效率。 - 调用 bw.flush():尤其在循环内定期刷新,避免因缓冲区未满导致写入延迟或中断时数据丢失。
- 避免在循环中创建新 PageRequest 实例:pageRequest.next() 已封装页码递增逻辑,简洁可靠。
-
数据库索引与排序至关重要:分页查询需基于稳定排序字段(如主键 id),否则可能漏查或重复。建议在 Repository 方法中显式指定排序:
@Query("SELECT d FROM Data d ORDER BY d.id") Slice<data> findAllByOrderById(Pageable pageable);</data> - 考虑事务与隔离级别:长时间运行的导出任务可能受数据库快照或锁影响,必要时可配置只读事务或调整隔离级别。
? 总结
该方案以极小改造成本(仅替换 findAll() 为分页调用),实现了内存友好、可中断、可监控的大数据导出流程。它不依赖 Spring Batch,完全兼容标准 Spring Boot 应用,是生产环境中处理海量数据导出的轻量级最佳实践。如后续有进度追踪、失败恢复等需求,可在循环中加入日志记录或断点续传逻辑进一步增强健壮性。











