Jdbc Thinドライバーを使用してOracleデータベースにBLOBを挿入するためにWebを検索すると、ほとんどのWebページで3ステップのアプローチが提案されています。
empty_blob()
値を挿入します。for update
の行を選択します。これは私にとってはうまくいきます、ここに例があります:
Connection oracleConnection = ...
byte[] testArray = ...
PreparedStatement ps = oracleConnection.prepareStatement(
"insert into test (id, blobfield) values(?, empty_blob())");
ps.setInt(1, 100);
ps.executeUpdate();
ps.close();
ps = oracleConnection.prepareStatement(
"select blobfield from test where id = ? for update");
ps.setInt(1, 100);
OracleResultSet rs = (OracleResultSet) ps.executeQuery();
if (rs.next()) {
BLOB blob = (BLOB) rs.getBLOB(1);
OutputStream outputStream = blob.setBinaryStream(0L);
InputStream inputStream = new ByteArrayInputStream(testArray);
byte[] buffer = new byte[blob.getBufferSize()];
int byteread = 0;
while ((byteread = inputStream.read(buffer)) != -1) {
outputStream.write(buffer, 0, byteread);
}
outputStream.close();
inputStream.close();
}
より簡単な1ステップのソリューションを使用することを著者が提案しているWebページがいくつかあります。このソリューションの前の例:
Connection oracleConnection = ...
byte[] testArray = ...
PreparedStatement ps = oracleConnection.prepareStatement(
"insert into test(id, blobfield) values(?, ?)");
BLOB blob = BLOB.createTemporary(oracleConnection, false, BLOB.DURATION_SESSION);
OutputStream outputStream = blob.setBinaryStream(0L);
InputStream inputStream = new ByteArrayInputStream(testArray);
byte[] buffer = new byte[blob.getBufferSize()];
int byteread = 0;
while ((byteread = inputStream.read(buffer)) != -1) {
outputStream.write(buffer, 0, byteread);
}
outputStream.close();
inputStream.close();
ps.setInt(1, 100);
ps.setBlob(2, blob);
ps.executeUpdate();
ps.close();
2番目のコードの方がはるかに簡単なので、私の質問は次のとおりです。最初の(人気のある)ソリューションのポイントは何ですか? 2番目のソリューションに何らかの制約がありましたか(ありましたか)(Oracleサーバーのバージョン番号、jdbcドライバーのバージョン、BLOBのサイズなど)?最初のソリューションの方が優れていますか(速度、メモリ消費など)?より単純な2番目のアプローチを使用しない理由はありますか?
まったく同じ質問がCLOBフィールドにも当てはまります。
最初のケースで述べた更新アプローチは、純粋なJDBCコードを使用して書き直すことができるため、Oracle固有のクラスへの依存を減らすことができます。これは、アプリをデータベースに依存しないようにする必要がある場合に役立ちます。
public static void updateBlobColumn(Connection con, String table, String blobColumn, byte[] inputBytes, String idColumn, Long id) throws SQLException {
PreparedStatement pStmt = null;
ResultSet rs = null;
try {
String sql =
" SELECT " + blobColumn +
" FROM " + table +
" WHERE " + idColumn + " = ? " +
" FOR UPDATE";
pStmt = con.prepareStatement(sql,
ResultSet.TYPE_FORWARD_ONLY,
ResultSet.CONCUR_UPDATABLE);
pStmt.setLong(1, id);
rs = pStmt.executeQuery();
if (rs.next()) {
Blob blob = rs.getBlob(blobColumn);
blob.truncate(0);
blob.setBytes(1, inputBytes);
rs.updateBlob(blobColumn, blob);
rs.updateRow();
}
}
finally {
if(rs != null) rs.close();
if(pStmt != null) pStmt.close();
}
}
MSSQLの場合、ロック構文が異なることを理解しています。
String sql =
" SELECT " + blobColumn +
" FROM " + table + " WITH (rowlock, updlock) " +
" WHERE " + idColumn + " = ? "
Oracle DBAからの別の視点。 Sunの人たちは、JDBC標準(1.0、2.0、3.0、4.0)を設計する際に非常に貧弱でした。 BLOBはラージオブジェクトを表すため、非常に大きくなる可能性があります。 JVMヒープに格納できないものです。 OracleはBLOBをファイルハンドルのようなものと見なしています(実際、これらは「LOBロケーター」と呼ばれています)。 LOBSはコンストラクターを介して作成できず、Javaオブジェクトではありません。また、LOBロケーター(Oracle.sql.BLOB)はコンストラクターを介して作成できません。これらはDB側で作成する必要があります。OracleではLOBを作成する方法は2つあります。
DBMS_LOB.CREATETEMPORATY-この場合、返されるロケーターは一時テーブルスペースを指します。このロケーターに対するすべての書き込み/読み取りは、ネットワークを介してDBサーバーに送信されます。 JVMヒープには何も格納されません。
EMPTY_BLOB関数を呼び出します。 INSERT INTO T1(NAME、FILE)VALUES( "a.avi"、EMPTY_BLOB())RETURNING FILE INTO?;この場合、返されるロブロケーターはデータテーブルスペースを指します。このロケーターに対するすべての書き込み/読み取りは、ネットワークを介してDBサーバーに送信されます。すべての書き込みは、REDOログへの書き込みによって「保護」されます。 JVMヒープには何も格納されません。 return句はJDBC標準(1.0、2.0)でサポートされていなかったため、インターネット上で「INSERT ...; SELECT ... FOR UPDATE;」という2つのステップのアプローチを推奨する多くの例を見つけることができます。
Oracle LOBは一部のデータベース接続に関連付ける必要があります。DB接続が失われた/閉じられた(または「コミットされた」)場合は使用できません。それらをある接続から別の接続に渡すことはできません。
2番目の例は機能しますが、一時表領域からデータ表領域へのデータの場合、過度のコピーが必要になります。
OracleサーバーのLOB処理はかなり貧弱であり、重大なパフォーマンスの問題(たとえば、REDOログの大量の過剰使用)に悩まされる可能性があるため、最初の解決策はそれらに対処する方法である可能性があります。
両方のアプローチを試すことをお勧めします。有能なDBAがいる場合は、サーバーへの影響が最も少ないアプローチをアドバイスできる場合があります。
JDBCの興味深い点の1つは、最新のドライバーに積極的にアップグレードして、JDBC 4.0の機能を操作できることです。 Oracle JDBCドライバーは古いバージョンのデータベースで動作するため、10gデータベースに対して11gブランドのJDBCドライバーを使用できます。 Oracleデータベース11g JDBCには、2つのフレーバーがあります。Java 5(つまり、JDK 1.5)のojdbc5.jarとJava 6(つまり、 JDK 1.6)。ojdbc6.jarは新しいJDBC 4.0仕様をサポートしています。
新しいdrivers/jdbc 4.0では、接続オブジェクトからBlobとClobsを作成できます。
Blob aBlob = con.createBlob();
int numWritten = aBlob.setBytes(1, val);
この文 :
blob.setBytes(1, inputBytes);
Oracleシンクライアントojdbc14.jarを使用すると問題が発生する、「サポートされていない機能」
したがって、私は次の方法で回避する必要がありました:
rset.next();
Blob bobj = rset.getBlob(1);
BLOB object = (BLOB) bobj;
int chunkSize = object.getChunkSize();
byte[] binaryBuffer = new byte[chunkSize];
int position = 1;
int bytesRead = 0;
int bytesWritten = 0, totbytesRead = 0, totbytesWritten = 0;
InputStream is = fileItem.getInputStream();
while ((bytesRead = is.read(binaryBuffer)) != -1) {
bytesWritten = object.putBytes(position, binaryBuffer, bytesRead);
position += bytesRead;
totbytesRead += bytesRead;
totbytesWritten += bytesWritten;
is.close();
CLOBデータが爆発することなくメモリに収まるほど小さい場合、準備されたステートメントを作成し、単に呼び出すことができます
ps.setString(1, yourString);
他のサイズ制限があるかもしれませんが、私たちが扱っているサイズ(最大500kB)で機能するようです。
2番目の解決策のためにいくつかの監視
私はojdbc6.jarを使用しています-最新リリースであり、「2番目のソリューション」からのステートメント:
BLOB blob = BLOB.createTemporary(oracleConnection, false, BLOB.DURATION_SESSION);
ステートメントが完了した後、blobを解放する必要があります。そうしないと、セッションが閉じられたときにblobが閉じられます(接続プールでは時間がかかる場合があります)。
blob.freeTemporary();
そうでなければ、ロックされたリソースを見ることができます:
select * from v$temporary_lobs
一時BLOBのもう1つの問題は、一時テーブルスペースを割り当てる必要があることです。ドキュメントに従って http://docs.Oracle.com/cd/E11882_01/appdev.112/e18294.pdf
一時LOBの一時テーブルスペースの管理一時テーブルスペースは、一時LOBデータを格納するために使用されます
私の場合、setObject(pos, byte[])
への単純な呼び出しが機能することがわかりました。 From JDBCとJavaを使用したデータベースプログラミング George Reese著
byte[] data = null;
stmt = con.prepareStatement("INSERT INTO BlobTest(fileName, "
+ "blobData) VALUES(?, ?)");
stmt.setString(1, "some-file.txt");
stmt.setObject(2, data, Types.BLOB);
stmt.executeUpdate();
BLOB挿入のサイズがblob.getBufferSize()より大きい場合、最初のチャンクがjdbc接続のautoCommitプロパティのデフォルト値はtrueであり、dbが新しいトランザクションとして扱うため、それ以降のチャンクの書き込みは失敗します。次のように推奨されます。
a)jdbc接続のautoCommitプロパティをfalseに設定します。
conn.setAutoCommit(false);
b)BLOB全体をアップロードした後、トランザクションを明示的にコミットします。
while ((bytesRead = messageInputStream.read(buffer)) != -1) {
cumBytes += bytesRead;
blobOutputStream.write(buffer, 0, bytesRead);
}
conn.commit();