web-dev-qa-db-ja.com

複雑すぎるOracle jdbc BLOB処理

Jdbc Thinドライバーを使用してOracleデータベースにBLOBを挿入するためにWebを検索すると、ほとんどのWebページで3ステップのアプローチが提案されています。

  1. empty_blob()値を挿入します。
  2. for updateの行を選択します。
  3. 実際の値を挿入します。

これは私にとってはうまくいきます、ここに例があります:

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フィールドにも当てはまります。

31
asalamon74

最初のケースで述べた更新アプローチは、純粋な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つあります。

  1. DBMS_LOB.CREATETEMPORATY-この場合、返されるロケーターは一時テーブルスペースを指します。このロケーターに対するすべての書き込み/読み取りは、ネットワークを介してDBサーバーに送信されます。 JVMヒープには何も格納されません。

  2. 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番目の例は機能しますが、一時表領域からデータ表領域へのデータの場合、過度のコピーが必要になります。

7
Ivan

OracleサーバーのLOB処理はかなり貧弱であり、重大なパフォーマンスの問題(たとえば、REDOログの大量の過剰使用)に悩まされる可能性があるため、最初の解決策はそれらに対処する方法である可能性があります。

両方のアプローチを試すことをお勧めします。有能なDBAがいる場合は、サーバーへの影響が最も少ないアプローチをアドバイスできる場合があります。

5
skaffman

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);
5
Brian

この文 :

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();
4
Yinch

CLOBデータが爆発することなくメモリに収まるほど小さい場合、準備されたステートメントを作成し、単に呼び出すことができます

ps.setString(1, yourString);

他のサイズ制限があるかもしれませんが、私たちが扱っているサイズ(最大500kB)で機能するようです。

2
Quartz

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データを格納するために使用されます

2
j23

私の場合、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();
1
Kirby

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();
0
AVA