oracle blob流式读取必须用getbinarystream(),禁用getbytes();写入须先insert empty_blob()再for update获取oracle.sql.blob,通过setbinarystream分块操作,全程绑定事务与连接,启用oracle.jdbc.usefetchsizewithlongcolumn=true参数优化。

Oracle BLOB流式读取必须用 getBinaryStream(),不能用 getBytes()
直接调用 getBytes() 会把整个BLOB一次性加载进内存,哪怕文件是500MB,JVM就可能OOM。Oracle JDBC驱动对BLOB的默认行为是“延迟加载+服务端游标保持”,但前提是必须用流接口。
实操要点:
-
ResultSet要在Connection未关闭、事务未提交前获取流,否则抛SQLException: Closed Connection - 务必调用
setFetchSize(0)或显式设置较大值(如1024 * 1024),避免驱动内部缓存整块LOB - 不要对同一
Blob对象反复调用getBinaryStream()—— Oracle驱动不保证多次打开流的安全性,应只开一次并复用
写入BLOB要用 setBinaryStream(1) 配合 PreparedStatement
别用 setBytes() 或 setBlob(),前者内存爆炸,后者在某些Oracle版本(如12c旧补丁)中会触发临时LOB自动清理,导致写入中途失败。
关键步骤:
- 先执行空插入(
INSERT INTO t (id, content) VALUES (?, empty_blob())),再用SELECT content FROM t WHERE id = ? FOR UPDATE获取可更新的Blob - 调用
blob.setBinaryStream(1)得到OutputStream,然后分块写入(建议每块8192字节) - 写完后必须显式
flush()和close(),否则部分数据可能滞留在驱动缓冲区 - 整个过程必须在同一个事务内完成,否则
empty_blob()占位会被回滚,后续FOR UPDATE查不到记录
注意Oracle LOB Locator生命周期和事务绑定
Oracle的BLOB不是数据本身,而是一个指向数据库存储位置的locator。这个locator只在当前事务有效,且与原始 Connection 强绑定。
常见踩坑点:
- 把
Blob对象序列化或跨线程传递 —— locator会失效,读写时抛ORA-01002: fetch out of sequence - 在Spring @Transactional方法里,用
JdbcTemplate查询出Blob后,又在另一个非事务方法里尝试读流 —— 此时事务已结束,locator无效 - 使用连接池(如HikariCP)时,不要在
finally块里手动close()Connection—— 这会提前释放locator,应交由Spring或连接池管理
流式处理需配合Oracle JDBC参数 oracle.jdbc.useFetchSizeWithLongColumn=true
这是Oracle官方文档里容易被忽略的开关。默认为 false,意味着即使你设了 setFetchSize(),驱动仍可能无视它对LONG、BLOB列的优化。
启用方式有两种:
- JDBC URL里加参数:
?oracle.jdbc.useFetchSizeWithLongColumn=true - 或代码中设置:
System.setProperty("oracle.jdbc.useFetchSizeWithLongColumn", "true")(需在加载驱动前)
开启后,setFetchSize() 对BLOB流的底层网络分包和缓冲才有实际影响,否则驱动仍可能一次拉取过多数据。
真正难的不是语法,是记住:BLOB流的生命完全依附于事务和连接,断了哪一环,流就立刻失效。写的时候别想着“先取对象再慢慢处理”,得把流读写逻辑压进最小事务边界里。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











