web-dev-qa-db-fra.com

Gestion des BLOBs Oracle jdbc trop compliquée

Lorsque je recherche sur le Web l'insertion de BLOB dans la base de données Oracle avec le pilote léger jdbc, la plupart des pages Web suggèrent une approche en 3 étapes:

  1. insérer la valeur empty_blob().
  2. sélectionnez la ligne avec for update.
  3. insérez la valeur réelle.

Cela fonctionne bien pour moi, voici un exemple:

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

Les auteurs suggèrent l’utilisation d’une solution simple en une étape sur certaines pages Web. Exemple précédent avec cette solution:

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

Le deuxième code est beaucoup plus facile, alors ma question est la suivante: quel est le point de la première solution (populaire)? Existe-t-il (existait-il) une sorte de contrainte pour la deuxième solution (numéro de version du serveur Oracle, version du pilote jdbc, taille du blob, ...)? La première solution est-elle meilleure (vitesse, consommation de mémoire, ...)? Des raisons pour ne pas utiliser la deuxième approche plus simple?

La même question s'applique aux champs CLOB.

31
asalamon74

L'approche de mise à jour que vous avez mentionnée dans le premier cas peut être réécrite à l'aide de code JDBC pur, réduisant ainsi votre dépendance aux classes spécifiques à Oracle. Cela pourrait être utile si votre application doit être agnostique pour les bases de données.

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

Pour MSSQL, je comprends que la syntaxe de verrouillage est différente:

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

Un autre point de vue du DBA Oracle. Les gars de Sun ont fait un très mauvais travail en concevant les normes JDBC (1.0, 2.0, 3.0, 4.0). BLOB signifie gros objet et peut donc être très grand. C'est quelque chose qui ne peut pas être stocké dans la pile JVM. Oracle considère les objets BLOB comme une sorte de descripteur de fichier (il s’appelle alors "localisateur de lob"). Les objets LOB ne peuvent pas être créés via le constructeur et ne sont pas des objets Java. De plus, les localisateurs de LOB (Oracle.sql.BLOB) ne peuvent pas être créés via le constructeur - ils DOIVENT être créés du côté de la base de données . Dans Oracle, il existe deux façons de créer un LOB.

  1. DBMS_LOB.CREATETEMPORATY - le localisateur renvoyé dans ce cas pointe vers un espace de table temporaire. Toutes les écritures/lectures sur ce localisateur seront envoyées via le réseau sur le serveur de base de données. Rien n'est stocké dans la pile JVM.

  2. Appel de la fonction EMPTY_BLOB . INSERER DANS LES VALEURS T1 (NOM, FICHIER) ("a.avi", EMPTY_BLOB ()) RETOURNER LE FICHIER EN? Toutes les écritures/lectures sur ce localisateur seront envoyées via le réseau sur le serveur de base de données. Toutes les écritures sont "gardées" par des écritures dans les fichiers de restauration. Rien n'est stocké dans la pile JVM. La clause de retour n'était pas supportée par les normes JDBC (1.0, 2.0), vous pouvez donc trouver de nombreux exemples sur Internet où les gens recommandent l'approche en deux étapes: "INSERT ...; SELECT ... FOR UPDATE;"

Les lobs Oracle doivent être associés à une connexion de base de données. Ils ne peuvent pas être utilisés lorsque La connexion à la base de données est perdue/fermée/(ou "validée"). Ils ne peuvent pas être transmis d'une connexion à une autre.

Votre deuxième exemple peut fonctionner, mais nécessitera une copie excessive si les données d'un espace table temporaire sont insérées dans un espace table de données.

7
Ivan

La gestion des objets métier du serveur Oracle est plutôt médiocre et peut entraîner de graves problèmes de performances (par exemple une utilisation excessive du journal de rétablissement). La première solution peut donc être un moyen de les résoudre.

Je suggérerais d'essayer les deux approches. Si vous disposez d'un administrateur de base de données compétent, ils peuvent vous indiquer quelle approche a le moins d'impact sur le serveur.

5
skaffman

Une chose intéressante avec JDBC est que vous pouvez effectuer une mise à niveau assez agressive vers les derniers pilotes et utiliser les fonctionnalités de JDBC 4.0. Les pilotes JDBC Oracle fonctionnent avec les anciennes versions de base de données. Vous pouvez donc utiliser un pilote JDBC de marque 11g sur une base de données 10g. La base de données Oracle 11g JDBC est disponible en deux versions: ojdbc5.jar pour Java 5 (JDK 1.5) et ojdbc6.jar pour Java 6 (JDK 1.6). Ojdbc6.jar prend en charge la nouvelle spécification JDBC 4.0.

Avec les nouveaux pilotes/jdbc 4.0, vous pouvez créer des blobs et des clobs à partir de l'objet de connexion:

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

Cette déclaration :

blob.setBytes(1, inputBytes);

donne des problèmes lorsque j'utilise le client léger Oracle ojdbc14.jar, "Fonctionnalités non prises en charge"

Donc, je devais travailler par:

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

Si les données CLOB sont suffisamment petites pour tenir dans votre mémoire sans exploser, vous pouvez simplement créer une instruction préparée et simplement appeler 

ps.setString(1, yourString);

Il y a peut-être d'autres limites de taille, mais cela semble fonctionner pour les tailles avec lesquelles nous traitons (500 Ko maximum).

2
Quartz

Quelques observateurs trouvés pour la deuxième solution

J'utilise ojdbc6.jar - la dernière version et la déclaration de "la deuxième solution":

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

Je dois libérer blob une fois la déclaration terminée - sinon, le blob est fermé lorsque la session est fermée (ce qui peut prendre beaucoup de temps avec le regroupement des connexions).

blob.freeTemporary();

Sinon, vous pouvez voir les ressources verrouillées:

select * from v$temporary_lobs

Un autre problème lié aux objets BLOB temporaires est la nécessité d'allouer un espace de table temporaire: Selon documentation http://docs.Oracle.com/cd/E11882_01/appdev.112/e18294.pdf

Gestion de l'espace de table temporaire pour les objets LOB temporaires L'espace de table temporaire permet de stocker des données LOB temporaires

2
j23

J'ai trouvé un simple appel à setObject(pos, byte[]) qui fonctionne pour mon cas. From Programmation de bases de données avec JDBC et Java Par 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

Si la taille de l'insertion de BLOB est supérieure à blob.getBufferSize () , la transaction est validée dès que le premier bloc est écrit dans la base de données sous la forme la valeur par défaut de la propriété autoCommit de la connexion jdbc est vraie et que les autres blocs écrivent comme db les traite comme de nouvelles transactions. Il est suggéré comme suit:
a) Définissez la propriété autoCommit de la connexion jdbc sur false. 

conn.setAutoCommit(false);

b) Valide explicitement la transaction après le téléchargement du BLOB complet.

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