it-swarm.com.de

Unkomplizierte Oracle jdbc BLOB-Behandlung

Wenn ich im Web nach dem Einfügen von BLOBs in die Oracle-Datenbank mit dem jdbc-Thin-Treiber suche, schlagen die meisten Webseiten einen Ansatz in drei Schritten vor:

  1. empty_blob() Wert einfügen.
  2. wählen Sie die Zeile mit for update aus.
  3. geben Sie den tatsächlichen Wert ein.

Das funktioniert gut für mich, hier ist ein Beispiel:

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();
}

Es gibt einige Webseiten, auf denen die Autoren eine einfachere 1-Schritt-Lösung vorschlagen. Vorheriges Beispiel mit dieser Lösung:

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

Der zweite Code ist viel einfacher, also meine Frage: Was ist der Punkt der ersten (populären) Lösung? Gibt es (gab es) eine Art Einschränkung für die zweite Lösung (Oracle-Server-Versionsnummer, Jdbc-Treiberversion, Größe des Blobs, ...)? Ist die erste Lösung besser (Geschwindigkeit, Speicherverbrauch, ...)? Gibt es Gründe, warum Sie den einfacheren zweiten Ansatz nicht verwenden?

Die gleiche Frage gilt für CLOB-Felder.

31
asalamon74

Der im ersten Fall erwähnte Aktualisierungsansatz kann mit reinem JDBC-Code umgeschrieben werden, wodurch die Abhängigkeit von Oracle-spezifischen Klassen verringert wird. Dies kann hilfreich sein, wenn Ihre App datenbankunabhängig sein muss.

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();
  }
}

Für MSSQL verstehe ich, dass die Sperrsyntax anders ist:

String sql = 
  " SELECT " + blobColumn + 
  " FROM " + table + " WITH (rowlock, updlock) " + 
  " WHERE " + idColumn + " = ? "

Ein weiterer Standpunkt von Oracle DBA. Die Jungs von Sun haben sehr schlechte Arbeit geleistet, als sie JDBC-Standards (1.0, 2.0, 3.0, 4.0) entwickelten. BLOB steht für large object und kann daher sehr groß sein. Es ist etwas, das nicht in JVM-Heap gespeichert werden kann. Oracle betrachtet BLOBs als etwas wie Dateihandles (es heißt, sie heißen dann "Lob Locators"). LOBS können nicht über den Konstruktor erstellt werden und sind keine Java-Objekte. LOB-Locators (Oracle.sql.BLOB) können auch nicht über den Konstruktor erstellt werden - sie MÜSSEN auf der DB-Seite erstellt werden. In Oracle gibt es zwei Möglichkeiten, ein LOB zu erstellen.

  1. DBMS_LOB.CREATETEMPORATY - Der in diesem Fall zurückgegebene Locator zeigt in einen temporären Tabellenbereich. Alle Schreib-/Lesevorgänge für diesen Locator werden über das Netzwerk an den DB-Server gesendet. Im JVM-Heap ist nichts gespeichert.

  2. Aufruf der Funktion EMPTY_BLOB . INSERT IN T1 (NAME, FILE) VALUES ("a.avi", EMPTY_BLOB ()) FILE INTO ZURÜCKKEHREN;;. Alle Schreib-/Lesevorgänge für diesen Locator werden über das Netzwerk an den DB-Server gesendet. Alle Schreibvorgänge werden durch Schreibvorgänge in Wiederholungsprotokollen "geschützt". Im JVM-Heap ist nichts gespeichert. Die Rückgabeklausel wurde von den JDBC-Standards (1.0, 2.0) nicht unterstützt. Daher finden Sie im Internet viele Beispiele, in denen die Benutzer zwei Schritte empfehlen: "INSERT ...; SELECT ... FOR UPDATE;"

Oracle-Lobs müssen mit einer Datenbankverbindung verbunden sein. Sie können nicht verwendet werden, wenn die DB-Verbindung unterbrochen wird (/ "Commited"). Sie können nicht von einer Verbindung zur anderen weitergegeben werden.

Das zweite Beispiel kann funktionieren, erfordert jedoch ein übermäßiges Kopieren, wenn Daten aus einem temporären Tabellenbereich in den Daten-Tabellenbereich kopiert werden.

7
Ivan

Die Verarbeitung von LOBs auf dem Oracle-Server ist ziemlich schlecht und kann zu erheblichen Leistungsproblemen führen (z. B. massive Überbeanspruchung des Redo-Logs). Daher kann die erste Lösung eine Lösung für diese Probleme sein.

Ich würde vorschlagen, beide Ansätze auszuprobieren. Wenn Sie über einen kompetenten Datenbankadministrator verfügen, können sie möglicherweise mitteilen, welcher Ansatz die geringsten Auswirkungen auf den Server hat.

5
skaffman

Eine interessante Sache mit JDBC ist, dass Sie ein aggressives Upgrade auf die neuesten Treiber durchführen und mit JDBC 4.0-Funktionen arbeiten können. Die Oracle-JDBC-Treiber funktionieren mit älteren Datenbankversionen, sodass Sie einen JDBC-Treiber mit 11 g Marke für eine 10 g-Datenbank verwenden können. Die Oracle-Datenbank 11g-JDBC ist in zwei Ausführungen erhältlich: ojdbc5.jar für Java 5 (d. H. JDK 1.5) und ojdbc6.jar für Java 6 (d. H. JDK 1.6). Die Datei ojdbc6.jar unterstützt die neue JDBC 4.0-Spezifikation.

Mit den neueren Treibern/jdbc 4.0 können Sie Blobs und Clobs aus dem Verbindungsobjekt erstellen:

Blob aBlob = con.createBlob();
int numWritten = aBlob.setBytes(1, val);
5
Brian

Diese Aussage :

blob.setBytes(1, inputBytes);

gibt Probleme, wenn ich Oracle Thin Client ojdbc14.jar verwende, "Nicht unterstützte Funktionen"

Also musste ich umgehen:

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

Vorausgesetzt, die CLOB-Daten sind klein genug, um in Ihren Speicher zu passen, ohne zu explodieren, können Sie einfach eine vorbereitete Anweisung erstellen und einfach aufrufen 

ps.setString(1, yourString);

Es kann andere Größenbeschränkungen geben, aber es scheint bei den Größen zu funktionieren, mit denen wir es zu tun haben (max. 500kB).

2
Quartz

Einige Watchouts fanden die zweite Lösung

Ich verwende ojdbc6.jar - die neueste Version und für die Aussage von 'the second solution':

BLOB blob = BLOB.createTemporary(oracleConnection, false, BLOB.DURATION_SESSION);

Ich muss Blob freigeben, nachdem die Anweisung abgeschlossen ist. Andernfalls wird Blob geschlossen, wenn die Sitzung geschlossen wird (dies kann beim Verbindungs-Pooling lange dauern).

blob.freeTemporary();

Ansonsten können Sie gesperrte Ressourcen sehen:

select * from v$temporary_lobs

Ein weiteres Problem bei temporären BLOBs besteht in der Notwendigkeit, temporären Tablespace zuzuordnen: Laut Dokumentation http://docs.Oracle.com/cd/E11882_01/appdev.112/e18294.pdf

Temporären Tabellenbereich für temporäre LOBs verwalten Temporärer Tabellenbereich wird zum Speichern temporärer LOB-Daten verwendet.

2
j23

Ich habe einen einfachen Aufruf an setObject(pos, byte[]) works für meinen Fall gefunden. Von Datenbankprogrammierung mit JDBC und Java Von 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

Wenn Größe des Einfügens von BLOB größer als blob.getBufferSize () ist, wird die Transaktion festgeschrieben, sobald der erste Block in db geschrieben wird, da der Standardwert der autoCommit-Eigenschaft der jdbc-Verbindung true ist und weitere Chunks-Schreibvorgänge fehlschlagen Als DB behandelt sie sie als neue Transaktionen. Es wird wie folgt vorgeschlagen:
a) Setzen Sie die AutoCommit-Eigenschaft für die Jdbc-Verbindung auf false. 

conn.setAutoCommit(false);

b) Bestätigen Sie die Transaktion nach dem Hochladen des gesamten BLOBs explizit.

while ((bytesRead = messageInputStream.read(buffer)) != -1) {
     cumBytes += bytesRead;
     blobOutputStream.write(buffer, 0, bytesRead);
    }
conn.commit();
0
AVA