Oracle JDBC驱动写BLOB必须用setBinaryStream()流式写入,先INSERT EMPTY_BLOB()占位,再SELECT FOR UPDATE获取可写句柄,分块写入8192字节缓冲区,事务内完成,且需检查数据库LOB chunk size配置。

Oracle JDBC驱动对BLOB写入有流式限制,必须用 setBinaryStream() 而非 setBytes()
直接调用 setBytes() 会把整个文件加载进 JVM 堆内存,1GB 文件大概吃掉 1.2GB 堆空间,极易触发 OutOfMemoryError。Oracle 官方驱动(ojdbc8+)明确要求大文件走流式写入,底层才能绑定到数据库服务端的临时 LOB 段。
实操要点:
- 必须先执行 INSERT 语句插入空
EMPTY_BLOB()占位,再通过SELECT ... FOR UPDATE获取可写 BLOB 句柄 - 从
ResultSet中调用getBlob()后,立刻调用blob.setBinaryStream(1)获取输出流 —— 这个流才是真正的数据库直连通道 - 不要用
FileInputStream包一层再塞进去;应直接把文件Path或InputStream分块写入该流,每次write()都实时刷到 Oracle 服务端 - 务必在事务内完成:INSERT → SELECT FOR UPDATE → 流写入 → COMMIT;中断时未提交的 LOB 会被数据库自动清理,但事务外写入会失败
分块写入时缓冲区大小设为 8192 字节最稳妥
太小(如 1024)会导致频繁系统调用,吞吐下降明显;太大(如 65536)可能触发 Oracle 网络包截断或 JDBC 驱动内部缓冲溢出,尤其在使用 Oracle RAC 或带防火墙的环境里容易静默丢数据。
示例关键片段:
立即学习“Java免费学习笔记(深入)”;
你是 TypeScript 项目架构专家,专注于脚手架搭建生产就绪的 Node.js 和前端应用。生成包含现代工具链(pnpm、Vite、Next.js)、类型
try (PreparedStatement ps = conn.prepareStatement("INSERT INTO doc (id, content) VALUES (?, EMPTY_BLOB())")) {
ps.setString(1, "DOC-1001");
ps.execute();
}
conn.commit();
try (PreparedStatement ps = conn.prepareStatement("SELECT content FROM doc WHERE id = ? FOR UPDATE")) {
ps.setString(1, "DOC-1001");
try (ResultSet rs = ps.executeQuery()) {
if (rs.next()) {
Blob blob = rs.getBlob("content");
try (OutputStream os = blob.setBinaryStream(1);
InputStream is = Files.newInputStream(Paths.get("/tmp/large.pdf"))) {
byte[] buf = new byte[8192];
int len;
while ((len = is.read(buf)) != -1) {
os.write(buf, 0, len);
}
}
}
}
}
conn.commit();
别忽略 Oracle 的 lob_chunk_size 和表空间配置
即使 Java 层写得正确,如果数据库侧 BLOB 所在表空间的默认 chunk size 是 8KB(常见于旧版建表脚本),而你要存 500MB 的视频,Oracle 内部会切出约 64000 个 chunk,导致 INSERT/UPDATE 性能陡降、undo 表空间压力飙升。
检查和调整方法:
- 查当前 chunk 大小:
SELECT table_name, chunk FROM user_lobs WHERE column_name = 'CONTENT' - 建表时显式指定:
content BLOB STORE AS SECUREFILE (CHUNK 32768)(32KB 更适合大文件) - 确保表空间是
AUTOALLOCATE或已预分配足够空间,否则 SecureFile LOB 在扩展时可能卡住 - 若用传统 BasicFile LOB,必须配
ENABLE STORAGE IN ROW关闭,否则超 4000 字节就报错
Spring Boot + JdbcTemplate 场景下要禁用默认 LobHandler
Spring 的 DefaultLobHandler 默认走 setBytes(),且不支持流式回写。哪怕你手动传 InputStream,它也会试图全量读入内存再转字节数组。
正确做法是绕过它:
- 不用
JdbcTemplate.update(String sql, Object... args)直接传InputStream - 改用
JdbcTemplate.execute(String sql, PreparedStatementCallback),自己控制PreparedStatement生命周期 - 或者升级到 Spring 6+,配合
OracleR2dbcClient使用响应式流(但需切换数据库驱动为 R2DBC)
真正麻烦的不是代码怎么写,而是很多人在测试环境用 2MB 文件没出问题,上线后写 800MB 视频就卡死或 OOM —— 因为没验证 LOB chunk 配置和驱动流式路径是否真实生效。

















