web-dev-qa-db-fra.com

Comment écrire/mettre à jour un blob Oracle de manière fiable?

J'essaie d'écrire et de mettre à jour un document PDF dans une colonne blob, mais je ne peux que mettre à jour le blob en écrivant plus de données que les données précédemment stockées ..__ Si j'essaie de mettre à jour la colonne de blob avec un document plus petit les données que je reçois seulement un pdf corrompu.

Tout d'abord, la colonne blob a été initialisée à l'aide de la fonction empty_blob (). J'ai écrit l'exemple de classe Java ci-dessous pour tester ce comportement. Je l'exécute pour la première fois avec 'true' comme premier paramètre de la méthode main . Ainsi, dans la première rangée, un document d'environ 31 Ko est stocké et dans la deuxième rangée, un document de 278 Ko . avec "false" comme paramètre, les deux lignes doivent donc être mises à jour en échangeant les documents. Le résultat est que je n'obtiens un résultat correct que lorsque j'écris plus de données que celles existantes.

Comment est-il possible d'écrire une méthode qui écrit et met à jour un blob de manière fiable sans s'inquiéter de la taille des données binaires?

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

Version Oracle: 11.1.0.7.0 - 64 bits

J'ai même essayé l'API JDBC standard sans utiliser celle spécifique d'Oracle (comme dans l'exemple ci-dessus) sans succès.

24
alexyz78

C'est beaucoup plus facile:

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

Cela fonctionne de la même manière lorsque vous utilisez une instruction INSERT. Pas besoin de empty_blob() et d'une deuxième déclaration de mise à jour.

29

En plus de a_horse_with_no_name 's answer (qui repose sur PreparedStatement.setBinaryStream (...) API), il existe au moins deux options supplémentaires pour les BLOB et trois autres pour les CLOB. et NCLOBs:

  1. Créez explicitement un LOB, écrivez-le et utilisez 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. Mettre à jour un LOB vide (inséré via DBMS_LOB.EMPTY_BLOB() ou DBMS_LOB.EMPTY_CLOB()) via SELECT ... FOR UPDATE. Ceci est spécifique à Oracle et nécessite deux instructions exécutées au lieu d'une. De plus, voici ce que vous tentiez d'accomplir en premier lieu:

    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. Pour les objets CLOB et NCLOB, vous pouvez également utiliser PreparedStatement.setString() et setNString(), respectivement.

3
Bass

FWIW, pour quelque chose qui tient dans la mémoire, j’ai trouvé que je pouvais simplement passer dans un tableau d’octets en tant que paramètre d’instruction préparée, plutôt que de passer par le "flux" de rigueur morale (ou pire, de choses spécifiques suggérées par Oracle)

En utilisant un wrapper "Modèle JDBC" Spring (org.springframework.jdbc.core.JdbcTemplate) pour placer le contenu d'une chaîne "volumineuse" (ou non) dans une colonne BLOB, le code ressemble à ce qui suit:

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

Il n'y a pas d'étape 2.

1
Roboprog