2011-12-01 19 views
18

Estoy tratando de escribir y actualizar un documento en PDF en una columna de blob, pero solo puedo actualizar el blob solo escribiendo más datos que los datos almacenados previamente. Si trato de actualizar la columna de blob con datos de documentos más pequeños, obtengo solo un pdf dañado.Cómo escribir/actualizar Oracle blob de manera confiable?

Primero, la columna de blob se ha inicializado utilizando la función empty_blob(). Escribí la clase Java de ejemplo a continuación para probar este comportamiento. Lo ejecuto la primera vez con 'verdadero' como primer parámetro del método principal, así que en la primera fila se almacena un documento de aproximadamente 31kB y en la segunda fila hay un documento de 278kB. Luego lo ejecuto con 'falso' como parámetro, de esta manera las dos filas deberían actualizarse intercambiando los documentos. El resultado es que obtengo un resultado correcto solo cuando escribo más datos que el existente.

¿Cómo es posible escribir un método que escribe y actualiza un blob de manera confiable sin preocuparse por el tamaño de los datos binarios?

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

versión de Oracle: 11.1.0.7.0 - 64 bits

Incluso probé la API JDBC estándar sin necesidad de utilizar uno específico de Oracle (como en el ejemplo anterior) sin ningún éxito.

+1

Quizás me falta algo obvio ...Pero, ¿dónde se define el método 'copy'? Mi corazonada es que el método 'copy' no está haciendo lo que esperabas y solo está reemplazando los primeros N bytes si los nuevos datos son más pequeños que los datos anteriores. –

+0

Por cierto: no debe abrir una nueva conexión para cada llamada de la función. Eso va a ser muy lento. –

+0

@JustinCave 'método de copia 'se define en [org.apache.commons.io.IOUtils] (http://commons.apache.org/io/api-release/org/apache/commons/io/IOUtils.html #copy (java.io.InputStream,% 20java.io.OutputStream)) clase. Hay una importación estática al comienzo del código. Las importaciones estáticas son menos legibles, pero creo que en algunos casos es conveniente usarlas. – alexyz78

Respuesta

24

Es mucho más fácil:

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

funciona de la misma cuando se utiliza una instrucción INSERT. No es necesario empty_blob() y una segunda declaración de actualización.

+0

Gracias a que sus soluciones funcionan perfectamente (incluso si la firma correcta de 'setBinaryStream' es' (int parameterIndex, InputStream x, int length) '). – alexyz78

+1

¿Qué sucede si se trata de un objeto Java y no de un archivo? – hari

+2

Hari: use un ObjectInputStream o un ByteArrayInputStream –

0

Fwiw, algo que cabe en la memoria, he encontrado que simplemente podría pasar en una matriz de bytes como parámetro declaración preparada, en lugar de ir a través de la "corriente" la moral de rigor (o peor Oracle específicos cosas/sugeridas)

Usando una envoltura Spring "JDBC template" (org.springframework.jdbc.core.JdbcTemplate) para poner el contenido de una cadena "grande" (o no) en una columna BLOB, el código es algo como lo siguiente:

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

no hay paso 2.

1

Además de a_horse_with_no_name 's answer (que se basa en PreparedStatement.setBinaryStream(...) API), hay al menos dos más opciones para los BLOB, y 3 más por CLOBs y NCLOBs:

  1. crear explícitamente un LOB, escribir en él y utilizar 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. actualizar un LOB vacío (introducido a través de DBMS_LOB.EMPTY_BLOB() o DBMS_LOB.EMPTY_CLOB()) a través de SELECT ... FOR UPDATE. Esto es específico de Oracle y requiere dos sentencias ejecutadas en lugar de una. Además, esto es lo que estaba tratando de lograr en primer lugar:

    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. Para CLOBs y NCLOBs, puede utilizar adicionalmente PreparedStatement.setString() y setNString(), respectivamente.

Cuestiones relacionadas