webentwicklung-frage-antwort-db.com.de

Wie schreibt/aktualisiert man Oracle Blob zuverlässig?

Ich versuche, ein PDF-Dokument in einer Blob-Spalte zu schreiben und zu aktualisieren, aber ich kann gerade den Blob aktualisieren, indem ich mehr Daten schreibe als die zuvor gespeicherten Daten .. Wenn ich versuche, die Blob-Spalte mit einem kleineren Dokument zu aktualisieren Daten bekomme ich nur ein beschädigtes pdf.

Zuerst wurde die Blobspalte mit der Funktion empty_blob () initialisiert. Ich habe die Java-Beispielklasse unten geschrieben, um dieses Verhalten zu testen. Ich führe es zum ersten Mal mit 'true' als ersten Parameter der main - Methode aus. In der ersten Zeile ist ein Dokument mit etwa 31 KB und in der zweiten Zeile ein Dokument mit 278 KB und dann gespeichert Wenn der Parameter 'false' ist, sollten die beiden Zeilen aktualisiert werden, indem die Dokumente ausgetauscht werden. Das Ergebnis ist, dass ich nur dann ein korrektes Ergebnis bekomme, wenn ich mehr Daten schreibe als die vorhandenen.

Wie ist es möglich, eine Methode zu schreiben, die einen Blob zuverlässig schreibt und aktualisiert, ohne sich über die Größe der Binärdaten Gedanken zu machen?

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-Version: 11.1.0.7.0 - 64-Bit

Ich habe sogar die Standard-JDBC-API ausprobiert, ohne Oracle-spezifische (wie im obigen Beispiel) ohne Erfolg zu verwenden.

24
alexyz78

Es ist viel einfacher:

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

Dies funktioniert auch bei Verwendung einer INSERT-Anweisung. Keine Notwendigkeit für empty_blob() und eine zweite Aktualisierungsanweisung.

29

Zusätzlich zu a_horse_with_no_name s answer (das auf PreparedStatement.setBinaryStream (...) API beruht) gibt es mindestens zwei weitere Optionen für BLOBs und 3 weitere für CLOBs und NCLOBs:

  1. Erstellen Sie explizit ein LOB, schreiben Sie es und verwenden Sie 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();
        }
    }
    
  2. Aktualisieren Sie ein leeres LOB (eingefügt über DBMS_LOB.EMPTY_BLOB() oder DBMS_LOB.EMPTY_CLOB()) über SELECT ... FOR UPDATE. Dies ist Oracle-spezifisch und erfordert, dass zwei Anweisungen statt einer ausgeführt werden. Darüber hinaus haben Sie dies in erster Linie versucht:

    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);
                    }
                }
            }
        }
    }
    
  3. Für CLOBs und NCLOBs können Sie zusätzlich PreparedStatement.setString() und setNString() verwenden.

3
Bass

FWIW, für etwas, das in den Speicher passt, habe ich festgestellt, dass ich einfach ein Bytearray als vorbereiteten Anweisungsparameter übergeben könnte, anstatt die "Strom" -Organität (oder schlimmer Oracle-spezifische/vorgeschlagene Dinge) durchzugehen.

Bei Verwendung eines Spring-JDBC-Template-Wrappers (org.springframework.jdbc.core.JdbcTemplate) zum Einfügen des Inhalts einer "großen" (oder nicht) Zeichenfolge in eine BLOB-Spalte sieht der Code etwa wie folgt aus:

jdbc.update( "insert into a_table ( clob_col ) values ( ? )", largeStr.getBytes() );

Es gibt keinen Schritt 2.

1
Roboprog