核心是构建“边读边解、边解边传、边传边存”的持续数据流,需协同文件流式读取、jdbc游标分页与批量提交、jvm容器感知内存控制及绕过orm对象膨胀。

Java 中批量数据库操作结合流式处理导入大文件,核心是切断“全量加载→内存驻留→批量写入”的错误链路,改为“边读边解、边解边传、边传边存”的持续数据流。这不是单纯调大批次或加内存能解决的,而是从文件读取、数据解析、JDBC 交互到 JVM 资源分配的端到端协同设计。
文件层:用流式读取替代全量加载
无论文件是 CSV、JSON、Excel 还是自定义分隔文本,都必须避免 Files.readAllLines() 或 WorkbookFactory.create(inputStream) 这类一次性加载行为。实际内存占用常达文件体积的 3–5 倍。
- CSV:用
OpenCSV的CsvToBeanBuilder配合LineCallbackHandler,或univocity-parsers的RowProcessor实现逐行回调 - Excel:优先选 Apache Fesod(非 POI),用
FesodSheet.read(..., new PageReadListener() {...}),默认每页 100 行,内存恒定在 MB 级 - 大 JSON:不用 Jackson
ObjectMapper.readValue(),改用JsonParser流式解析,while (parser.nextToken() != JsonToken.END_ARRAY)手动推进 - 通用二进制/文本:用
BufferedReader+readLine()或InputStream+ 自定义分块读取,每次只持有一行或一个逻辑记录
JDBC 层:流式拉取 + 游标分页 + 批量提交
数据库侧不能依赖 LIMIT OFFSET 分页(偏移越大越慢),也不能让 ResultSet 把全部结果缓存在客户端内存里。
- 读取端:MySQL 加
?useCursorFetch=true&fetchSize=1000,PostgreSQL 设statement.setFetchSize(1000)并配ResultSet.TYPE_FORWARD_ONLY;驱动会按需从服务端拉取,不囤积 - 写入端:禁用自动提交,
conn.setAutoCommit(false);每 500–2000 条调一次ps.executeBatch()+ps.clearBatch();单批完成后立即clear()引用,促 Minor GC - 分页逻辑:用游标分页代替偏移分页,例如
SELECT * FROM t WHERE id > ? ORDER BY id LIMIT 1000,下一批把上批最大 id 当作新 cursor - BLOB 处理:对超大二进制字段,必须用
ps.setBinaryStream(index, inputStream, length),严禁setBytes()或setBlob(byte[])
JVM 与连接池:控住堆外开销,防容器被 OOMKilled
Docker 容器内存超限,90% 是因为只管了堆内存(-Xmx),却放任元空间、线程栈、直接内存和连接池缓冲疯长。
- JVM 启用容器感知:
-XX:+UseContainerSupport(JDK 8u191+ 默认开启),再设-XX:MaxRAMPercentage=70.0让堆自动适配容器限制 - 压缩线程栈:
-Xss256k(默认 1MB),批量任务常启数十线程,省出上百 MB - 约束直接内存:
-XX:MaxDirectMemorySize=128m,尤其 HikariCP 和 MyBatis Batch 场景必加 - HikariCP 控连接数:
maximumPoolSize ≤ CPU 核数 × 2(如容器限制 2 核,则 ≤ 4),避免连接对象堆积和线程争抢 - 导入前临时优化 DB:MySQL 可关唯一校验、禁用非关键索引;事务日志调大,commit 频率控制在 100–500 条/次
框架层:绕过 ORM 的对象膨胀陷阱
MyBatis 或 JPA 在百万级导入中极易成为瓶颈——代理对象、懒加载、一级二级缓存、全字段映射都会制造大量冗余对象。
- 读取时不用
List<entity></entity>,改用Map<string object></string>或轻量 DTO,跳过代理生成与字段校验 - 写入时避免
@Insert+<foreach></foreach>拼 SQL,改用原生 JDBC PreparedStatement 批处理,减少框架解析开销 - 禁用 MyBatis 的二级缓存和延迟加载,
configuration.setLazyLoadingEnabled(false) - 若必须用 MyBatis,启用
Cursor<t></t>替代List<t></t>,配合@Select返回Cursor类型,底层走流式 ResultSet
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











