Blob列のpdfドキュメントを作成および更新しようとしていますが、以前に保存されたデータよりも多くのデータを書き込むだけでblobを更新できます。 blob列をより小さいドキュメントデータで更新しようとすると、破損したpdfのみが表示されます。
最初に、blob列はempty_blob()関数を使用して初期化されています。この動作をテストするために、サンプルJavaクラスを以下に記述しました。最初の行でmainメソッドの最初のパラメーターとして 'true'を指定して実行します約31kBのドキュメントが保存されており、2行目には278kBのドキュメントがあります。その後、「false」をパラメータとして実行し、この方法でドキュメントを交換して2行を更新する必要があります。結果は、既存のデータよりも多くのデータを書き込む場合にのみ発生します。
バイナリデータのサイズを気にせずに、信頼できる方法で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ビット
Oracleの特定のAPI(上記の例のように)を使用せずに標準の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()
と2番目の更新ステートメントは不要です。
a_horse_with_no_name の answer ( PreparedStatement.setBinaryStream(...) APIに依存)に加えて、少なくとも2つありますBLOBのオプション、およびCLOBとNCLOBの3つのオプション:
明示的に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();
}
}
_
_SELECT ... FOR UPDATE
_経由で空のLOB(DBMS_LOB.EMPTY_BLOB()
またはDBMS_LOB.EMPTY_CLOB()
経由で挿入)を更新します。これはOracle固有であり、1つではなく2つのステートメントを実行する必要があります。さらに、これは最初に達成しようとしていたものです。
_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()
を追加で使用できます。
FWIW、メモリに収まる何かのために、「ストリーム」の厳格な士気(またはより悪いOracle固有/提案されたもの)を通過するのではなく、準備されたステートメントパラメータとしてバイト配列を渡すことができることがわかりました
Springの「JDBCテンプレート」ラッパー(org.springframework.jdbc.core.JdbcTemplate)を使用して、「大きい」(またはない)文字列の内容をBLOB列に入れると、コードは次のようになります。
jdbc.update( "insert into a_table ( clob_col ) values ( ? )", largeStr.getBytes() );
手順2はありません。