如何以可靠的方式编写/更新Oracle blob?

我正在尝试在blob列中编写和更新pdf文档,但我只能更新blob,只写入比以前存储的数据更多的数据。 如果我尝试用较小的文档数据更新blob列,我只会得到一个损坏的pdf。

首先使用empty_blob()函数初始化blob列。 我在下面编写了示例Java类来测试此行为。 我第一次使用’true’作为main方法的第一个参数运行它,所以在第一行中存储了大约31kB的文档,在第二行中存储了一个278kB的文档。 然后我用’false’作为参数运行它,这样两行应该更新交换文件。 结果是,只有当我写入比现有数据更多的数据时,才能得到正确的结果。

如何编写一种以可靠的方式编写和更新blob的方法而不必担心二进制数据的大小?

import static org.apache.commons.io.IOUtils.copy; import java.io.FileInputStream; import java.io.OutputStream; import java.sql.Connection; import java.sql.DriverManager; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import oracle.jdbc.OracleDriver; import oracle.jdbc.OracleResultSet; import oracle.sql.BLOB; import org.apache.commons.lang.ArrayUtils; /** * Prerequisites: * 1) a table named 'x' must exists [create table x (i number, j blob);] * 2) that table should have two columns [insert into x (i, j) values (1, empty_blob()); insert into x (i, j) values (2, empty_blob()); commit;] * 3) download lsp.pdf from http://www.objectmentor.com/resources/articles/lsp.pdf * 4) download dotguide.pdf from http://www.graphviz.org/Documentation/dotguide.pdf */ public class UpdateBlob { public static void main(String[] args) throws Exception { processFiles(new String[]{"lsp.pdf", "dotguide.pdf"}, Boolean.valueOf(args[0])); } public static void processFiles(String [] fileNames, boolean forward) throws Exception { if(!forward){ ArrayUtils.reverse(a); } int idx = 1; for(String fname : fileNames){ insert(idx++, fname); } } private static void insert(int idx, String fname) throws Exception{ Connection conn = null; PreparedStatement ps = null; ResultSet rs = null; try { DriverManager.registerDriver(new OracleDriver()); conn = DriverManager.getConnection("jdbc:oracle:thin:@"+db+":"+port+":"+sid, user, pwd); ps = conn.prepareStatement("select j from x where i = ? for update"); ps.setLong(1, idx); rs = ps.executeQuery(); if (rs.next()) { FileInputStream instream = new FileInputStream(fname); BLOB blob = ((OracleResultSet)rs).getBLOB(1); OutputStream outstream = blob.setBinaryStream(1L); copy(instream, outstream); instream.close(); outstream.close(); } rs.close(); ps.close(); conn.close(); } catch (SQLException e) { e.printStackTrace(); throw new Exception(e); } } } 

Oracle版本:11.1.0.7.0 – 64位

我甚至尝试了标准的JDBC API,而没有使用Oracle的特定JDBC API(如上例所示),但没有成功。

它更容易:

 PreparedStatement pstmt = conn.prepareStatement("update blob_table set blob = ? where id = ?"); File blob = new File("/path/to/picture.png"); FileInputStream in = new FileInputStream(blob); // the cast to int is necessary because with JDBC 4 there is // also a version of this method with a (int, long) // but that is not implemented by Oracle pstmt.setBinaryStream(1, in, (int)blob.length()); pstmt.setInt(2, 42); // set the PK value pstmt.executeUpdate(); conn.commit(); pstmt.close(); 

使用INSERT语句时,它的工作方式相同。 不需要empty_blob()和第二个更新语句。

除了a_horse_with_no_name的答案 (依赖于PreparedStatement.setBinaryStream(…) API)之外,还有至少两个BLOB选项,还有3个用于CLOB和NCLOB:

  1. 显式创建一个LOB,写入它,并使用PreparedStatement.setBlob(int, Blob)

     int insertBlobViaSetBlob(final Connection conn, final String tableName, final int id, final byte value[]) throws SQLException, IOException { try (final PreparedStatement pstmt = conn.prepareStatement(String.format("INSERT INTO %s (ID, VALUE) VALUES (?, ?)", tableName))) { final Blob blob = conn.createBlob(); try (final OutputStream out = new BufferedOutputStream(blob.setBinaryStream(1L))) { out.write(value); } pstmt.setInt(1, id); pstmt.setBlob(2, blob); return pstmt.executeUpdate(); } } 
  2. 通过SELECT ... FOR UPDATE更新空LOB(通过DBMS_LOB.EMPTY_BLOB()DBMS_LOB.EMPTY_CLOB()插入DBMS_LOB.EMPTY_BLOB() 。 这是特定于Oracle的,需要执行两个语句而不是一个。 此外,这是您首先要完成的任务:

     void insertBlobViaSelectForUpdate(final Connection conn, final String tableName, final int id, final byte value[]) throws SQLException, IOException { try (final PreparedStatement pstmt = conn.prepareStatement(String.format("INSERT INTO %s (ID, VALUE) VALUES (?, EMPTY_BLOB())", tableName))) { pstmt.setInt(1, id); pstmt.executeUpdate(); } try (final PreparedStatement pstmt = conn.prepareStatement(String.format("SELECT VALUE FROM %s WHERE ID = ? FOR UPDATE", tableName))) { pstmt.setInt(1, id); try (final ResultSet rset = pstmt.executeQuery()) { while (rset.next()) { final Blob blob = rset.getBlob(1); try (final OutputStream out = new BufferedOutputStream(blob.setBinaryStream(1L))) { out.write(value); } } } } } 
  3. 对于CLOB和NCLOB,您还可以分别使用PreparedStatement.setString()setNString()

FWIW,对于适合内存的东西,我发现我可以简单地传入一个字节数组作为预处理语句参数,而不是通过“流”严格的士气(或更糟的Oracle特定/建议事物)

使用Spring“JDBC模板”包装器(org.springframework.jdbc.core.JdbcTemplate)将“大”(或非)字符串的内容放入BLOB列,代码如下所示:

 jdbc.update( "insert into a_table ( clob_col ) values ( ? )", largeStr.getBytes() ); 

没有第2步。