31.7. Stocker des données binaires

PostgreSQL fournit deux façons distinctes de stocker des données binaires. Elles peuvent être stockées dans une table en utilisant le type de données bytea ou en utilisant la fonctionnalité des gros objets qui stockent les données binaires dans une table séparée dans un format spécial et se réfère à cette table en stockant une valeur de type oid dans votre table.

Pour déterminer quelle méthode est appropriée, vous avez besoin de comprendre les limitations de chaque méthode. Le type de données bytea ne convient pas bien pour stocker de grosses quantités de données binaires. Alors qu'une colonne de type bytea peut contenir jusqu'à 1 Go de données binaires, elle demanderait une énorme quantité de mémoire pour traiter une telle valeur. La méthode des objets larges convient mieux pour stocker de très grands objets mais a ses propres limitations. Spécifiquement, supprimer une ligne contenant une référence de gros objet ne supprime pas le gros objet. Supprimer celui-ci est une opération qui doit être réalisée séparément. Les gros objets ont aussi quelques problèmes de sécurité car toute personne connectée à la base de données peut visualiser et/modifier tout gros objet même s'il n'a pas le droit de visualiser/mettre à jour la ligne contenant la référence au gros objet.

La version 7.2 a été la première à disposer d'un pilote JDBC supportant le type de données bytea. L'introduction de cette fonctionnalité dans la 7.2 a introduit une changement dans le comportement comparé aux précédentes versions. Depuis la 7.2, les méthodes getBytes(), setBytes(), getBinaryStream() et setBinaryStream() opèrent sur le type de données bytea. En 7.1 et pour les versions précédentes, ces méthodes opéraient sur le type de données oid associé avec chaque gros objet. Il est possible de revenir à l'ancien comportement de la version 7.1 en initialisant la propriété compatible de l'objet Connection avec la valeur 7.1.

Pour utiliser le type de données bytea, vous devez simplement utiliser les méthodes getBytes(), setBytes(), getBinaryStream() ou setBinaryStream().

Pour utiliser la fonctionnalité des gros objets, vous devez utiliser soit la classe LargeObject fournie par le pilote JDBC de PostgreSQL soit utiliser les méthodes getBLOB() et setBLOB().

Important : Vous devez accéder aux gros objets à l'intérieur d'un bloc de transaction SQL. Vous pouvez lancer un bloc de transaction en appelant setAutoCommit(false).

Note : Dans une prochaine version du pilote JDBC driver, les méthodes getBLOB() et setBLOB() n'interagiront plus avec les gros objets mais fonctionneront avec le type de données bytea. Donc, il vous est recommandé d'utiliser l'API de LargeObject si vous pensez utiliser des gros objets.

Exemple 31-8 contient quelques exemples sur le traitement de données binaires en utilisant le pilote JDBC de PostgreSQL.

Exemple 31-8. Traiter des données binaires avec JDBC

Par exemple, supposez que vous avez une table contenant des noms de fichiers d'images et que vous souhaitez aussi stocker l'image dans une colonne de type bytea :

CREATE TABLE images (nomimg text, img bytea);

Pour insérer une image, vous utiliserez :

File file = new File("monimage.gif");
FileInputStream fis = new FileInputStream(file);
PreparedStatement ps = conn.prepareStatement("INSERT INTO images VALUES (?, ?)");
ps.setString(1, file.getName());
ps.setBinaryStream(2, fis, file.length());
ps.executeUpdate();
ps.close();
fis.close();

Ici, setBinaryStream() transfère un certain nombre d'octets d'un flux dans la colonne de type bytea. Ceci pourrait aussi être fait en utilisant la méthode setBytes() si le contenu de l'image était déjà dans un byte[].

Récupérer une image est encore plus simple. (Nous utilisons PreparedStatement, mais la classe Statement pourrait très bien la remplacer.)

PreparedStatement ps = con.prepareStatement("SELECT img FROM images WHERE nomimg
= ?");
ps.setString(1, "monimage.gif");
ResultSet rs = ps.executeQuery();
if (rs != NULL) {
    while (rs.next()) {
        byte[] imgBytes = rs.getBytes(1);
        // utiliser les données ici...
    }
    rs.close();
}
ps.close();

Ici, les données binaires ont été récupérées dans un byte[]. À la place, vous pourriez avoir utilisé un objet InputStream.

Vous pouvez aussi stocker un très gros fichier et vouloir utiliser l'API LargeObject pour stocker le fichier :

CREATE TABLE imageslo (nomimg text, oidimg oid);

Pour insérer une image, vous pourriez :

// Tous les appels à l'API LargeObject doivent se faire dans un bloc de
// transaction
conn.setAutoCommit(false);

// Récupérer le gestionnaire des gros objets pour lui faire réaliser les
// opérations
LargeObjectManager lobj =
((org.postgresql.PGConnection)conn).getLargeObjectAPI();

// Créer un nouveau gros objet
int oid = lobj.create(LargeObjectManager.READ | LargeObjectManager.WRITE);

// Ouvrir le gros objet en écriture
LargeObject obj = lobj.open(oid, LargeObjectManager.WRITE);

// Maintenant, ouvrir le fichier
File file = new File("monimage.gif");
FileInputStream fis = new FileInputStream(file);

// Copier les données du fichier dans le gro objet
byte buf[] = new byte[2048];
int s, tl = 0;
while ((s = fis.read(buf, 0, 2048)) > 0) {
    obj.write(buf, 0, s);
    tl += s;
}

// Fermer le gros objet
obj.close();

// Maintenant, insérer la ligne dans imageslo
PreparedStatement ps = conn.prepareStatement("INSERT INTO imageslo VALUES (?, ?)");
ps.setString(1, file.getName());
ps.setInt(2, oid);
ps.executeUpdate();
ps.close();
fis.close();

Pour récupérer l'image du gros objet :

// Tous les appels à l'API LargeObject doivent se faire dans un bloc de
// transaction
conn.setAutoCommit(false);

// Récupérer le gestionnaire des gros objets pour lui faire réaliser les
// opérations
LargeObjectManager lobj =
((org.postgresql.PGConnection)conn).getLargeObjectAPI();

PreparedStatement ps = con.prepareStatement("SELECT oidimg FROM imageslo
WHERE nomimg = ?");
ps.setString(1, "monimage.gif");
ResultSet rs = ps.executeQuery();
if (rs != NULL) {
    while (rs.next()) {
        // Ouvrir le gros objet en lecture
        int oid = rs.getInt(1);
        LargeObject obj = lobj.open(oid, LargeObjectManager.READ);

        // Lire les données
        byte buf[] = new byte[obj.size()];
        obj.read(buf, 0, obj.size());
        // Utiliser les données ici

        // Fermer l'objet
        obj.close();
    }
    rs.close();
}
ps.close();