我正在尝试在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(如上例所示),但没有成功。
答案 0 :(得分:29)
这更容易:
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()
和第二次更新声明。
答案 1 :(得分:3)
除了a_horse_with_no_name answer(依赖PreparedStatement.setBinaryStream(...) API)之外,还有至少两个BLOB选项,还有3个选项用于BLOB CLOB和NCLOB:
明确创建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();
}
}
通过DBMS_LOB.EMPTY_BLOB()
更新空LOB(通过DBMS_LOB.EMPTY_CLOB()
或SELECT ... FOR UPDATE
插入)。这是特定于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);
}
}
}
}
}
对于CLOB和NCLOB,您还可以分别使用PreparedStatement.setString()
和setNString()
。
答案 2 :(得分:1)
FWIW,对于适合内存的东西,我发现我可以简单地传入一个字节数组作为预处理语句参数,而不是通过“流”严格的士气(或更糟糕的Oracle特定/建议事物)
使用Spring“JDBC模板”包装器(org.springframework.jdbc.core.JdbcTemplate)将“大”(或非)字符串的内容放入BLOB列中,代码如下所示:< / p>
jdbc.update( "insert into a_table ( clob_col ) values ( ? )", largeStr.getBytes() );
没有第2步。