Oracle数据库Java存储过程读取文件返回BLOB实现方法
Oracle 数据库无目录对象读写文件系统二进制文件(返回BLOB)实现方案
方案背景
需求为在Oracle数据库内部直接读写文件系统二进制文件,不使用Oracle目录对象(涉及目录数量过多,管理复杂度高),当前已持有文件存储根目录的Java读写权限,无安全限制。
早期实现为Java读取文件后转Base64字符串返回,配套PL/SQL函数可正常运行,但VARCHAR2/String类型存在长度上限,无法适配大体积二进制文件需求,先后尝试直接返回byte[]、返回SerialBlob两种方案均因类型映射问题失败。
早期可运行的Base64参考实现
Java侧代码:
public static String getFile(String directory, String filename) { try { File file = new File(directory + "/" + filename); byte[] fileContent = Files.readAllBytes(file.toPath()); return Base64.getEncoder().encodeToString(fileContent); } catch (IOException e) { throw new IllegalStateException("could not read file", e); } }
PL/SQL侧映射:
FUNCTION get_file (p_dir in varchar2, p_file in varchar2) RETURN VARCHAR2 AS LANGUAGE JAVA NAME 'FilesFromUnix.getFile (java.lang.String, java.lang.String) return java.lang.String';
早期失败尝试的核心问题
- 直接返回byte[]方案:PL/SQL映射存在两处错误,一是调用的Java方法名与新写的方法名不匹配,二是返回类型错写为
java.lang.Byte(单字节包装类),与byte[]数组类型完全不符;同时Oracle内置JVM不支持将Java原生byte[]自动映射为PL/SQL BLOB类型。 - 返回SerialBlob方案:
javax.sql.rowset.serial.SerialBlob是JDBC客户端场景使用的序列化BLOB实现,不属于Oracle内核识别的原生BLOB类型,无法直接映射为PL/SQL BLOB,会触发类型不兼容报错。
最终可行实现
核心思路是使用Oracle自带的oracle.sql.BLOB类作为中转,在Java存储过程中创建数据库原生临时BLOB,通过流拷贝将文件内容写入BLOB后返回,PL/SQL侧可直接识别为BLOB类型,无长度限制。
Java存储过程代码
代码依赖Oracle内置的JDBC驱动类,不需要额外引入第三方包,加载到Oracle JVM即可运行:
import java.io.File; import java.io.OutputStream; import java.io.InputStream; import java.nio.file.Files; import java.sql.Connection; import oracle.jdbc.OracleDriver; import oracle.sql.BLOB; public class FilesFromUnix { /** * 读取文件系统文件返回Oracle原生BLOB * @param directory 文件所在目录绝对路径 * @param filename 文件名 * @return 存储文件内容的BLOB * @throws Exception 读取失败时抛出异常 */ public static BLOB getFile(String directory, String filename) throws Exception { File targetFile = new File(directory, filename); if (!targetFile.exists() || !targetFile.isFile()) { throw new IllegalStateException("目标路径不存在或不是有效文件"); } // 获取数据库内部连接,创建会话级临时BLOB Connection conn = new OracleDriver().defaultConnection(); BLOB tempBlob = BLOB.createTemporary(conn, true, BLOB.DURATION_SESSION); tempBlob.open(BLOB.MODE_READWRITE); // 流方式拷贝文件内容到BLOB,避免大文件占满内存 try (OutputStream out = tempBlob.setBinaryStream(1L)) { Files.copy(targetFile.toPath(), out); } catch (Exception e) { // 异常时释放临时BLOB避免内存泄漏 tempBlob.freeTemporary(); throw new IllegalStateException("读取文件写入BLOB失败", e); } tempBlob.close(); return tempBlob; } /** * 将BLOB内容写入文件系统 * @param directory 目标目录绝对路径 * @param filename 目标文件名 * @param content 待写入的BLOB内容 * @throws Exception 写入失败时抛出异常 */ public static void writeFile(String directory, String filename, BLOB content) throws Exception { File targetFile = new File(directory, filename); // 自动创建不存在的父目录 if (targetFile.getParentFile() != null && !targetFile.getParentFile().exists()) { targetFile.getParentFile().mkdirs(); } // 流方式写入文件 try (InputStream in = content.getBinaryStream()) { Files.copy(in, targetFile.toPath()); } catch (Exception e) { throw new IllegalStateException("写入BLOB到文件失败", e); } } }
PL/SQL侧封装
注意映射的类型必须写全类名oracle.sql.BLOB,方法名、参数列表要和Java侧完全一致:
-- 读取文件返回BLOB的函数 CREATE OR REPLACE FUNCTION get_file (p_dir IN VARCHAR2, p_file IN VARCHAR2) RETURN BLOB AS LANGUAGE JAVA NAME 'FilesFromUnix.getFile(java.lang.String, java.lang.String) return oracle.sql.BLOB'; / -- 将BLOB写入文件的存储过程 CREATE OR REPLACE PROCEDURE write_file (p_dir IN VARCHAR2, p_file IN VARCHAR2, p_content IN BLOB) AS LANGUAGE JAVA NAME 'FilesFromUnix.writeFile(java.lang.String, java.lang.String, oracle.sql.BLOB)'; /
方案优势
- 不需要创建任何Oracle Directory对象,只要Java存储过程有对应文件路径的读写权限即可正常运行,适配多目录的管理场景。
- 采用流拷贝方式处理文件,不需要一次性把全文件加载到内存,支持GB级甚至TB级大文件,稳定性更高。
- 返回原生BLOB类型,上限和Oracle BLOB类型一致(最大支持128TB),不存在VARCHAR2的长度限制。
- 临时BLOB为会话级,会话结束后自动回收,异常分支也做了显式释放,不会造成内存泄漏。
内容的提问来源于stack exchange,提问作者nightfox79
相关产品推荐
相关产品推荐

