Comment récupérer des données avec ResultSet en Java?

Une base de données n’est pas utile si vous ne pouvez pas récupérer vos données. Nous commençons par vous montrer comment parcourir un ResultSet. Ensuite, nous passons en revue les différentes méthodes pour obtenir des colonnes par type.

Lecture d’un ResultSet

Lorsque vous travaillez avec un ResultSet, la plupart du temps, vous écrirez une boucle pour examiner chaque ligne. Le code ressemble à ceci :

String sql = "SELECT id, name FROM exhibits";
var idToNameMap = new HashMap<Integer, String>();

try (var ps = conn.prepareStatement(sql);
     ResultSet rs = ps.executeQuery()) {
     
    while (rs.next()) {
        int id = rs.getInt("id");
        String nom = rs.getString("name");
        idToNameMap.put(id, nom);
    }
    System.out.println(idToNameMap);
}

Cela produit :

{1=Éléphant d'Afrique, 2=Zèbre}

Il y a quelques points à noter ici. Tout d’abord, nous utilisons la méthode executeQuery() puisque nous voulons qu’un ResultSet soit retourné. Ensuite, nous parcourons les résultats en boucle. Chaque itération représente une ligne dans le ResultSet. Les lignes montrent la meilleure façon d’obtenir les colonnes pour une ligne donnée.

Un ResultSet possède un curseur, qui pointe vers l’emplacement actuel dans les données. La figure montre la position lorsque nous parcourons la boucle.

À la ligne, le curseur commence par pointer vers l’emplacement avant la première ligne du ResultSet. Lors de la première itération de la boucle, rs.next() renvoie true, et le curseur se déplace pour pointer vers la première ligne de données. Lors de la deuxième itération de la boucle, rs.next() renvoie true à nouveau, et le curseur se déplace pour pointer vers la deuxième ligne de données. Le prochain appel à rs.next() renvoie false. Le curseur avance au-delà de la fin des données. Le false signifie qu’il n’y a plus de données disponibles à récupérer.

Nous avons dit que la “meilleure façon” d’obtenir des données était avec les noms de colonnes. Il existe une autre façon d’accéder aux colonnes. Vous pouvez utiliser un index, en comptant à partir de 1 au lieu d’un nom de colonne.

int id = rs.getInt(1);
String nom = rs.getString(2);

Vous pouvez maintenant voir les positions des colonnes. Remarquez comment les colonnes sont comptées à partir de 1 plutôt que 0. Tout comme avec un PreparedStatement, JDBC commence à compter à 1 dans un ResultSet.

Le nom de colonne est préférable car il est plus clair ce qui se passe lors de la lecture du code. Il vous permet également de modifier le SQL pour réorganiser les colonnes.

Récupération d’une Seule Ligne

Parfois, vous voulez obtenir une seule ligne de la table. Peut-être que vous n’avez besoin que d’une donnée. Ou peut-être que le SQL ne renvoie que le nombre de lignes dans la table. Lorsque vous ne voulez qu’une seule ligne, vous utilisez une instruction if plutôt qu’une boucle while.

var sql = "SELECT count(*) FROM exhibits";
try (var ps = conn.prepareStatement(sql);
     var rs = ps.executeQuery()) {
    if (rs.next()) {
        int count = rs.getInt(1);
        System.out.println(count);
    }
}

Il est important de vérifier que rs.next() renvoie true avant d’essayer d’appeler un getter sur le ResultSet. Si une requête ne renvoie aucune ligne, elle lancerait une SQLException, donc l’instruction if vérifie qu’il est sûr d’appeler. Alternativement, vous pouvez utiliser le nom de la colonne.

var count = rs.getInt("count");

Essayons de lire une colonne qui n’existe pas.

var sql = "SELECT count(*) AS count FROM exhibits";
try (var ps = conn.prepareStatement(sql);
     var rs = ps.executeQuery()) {
    if (rs.next()) {
        var count = rs.getInt("total");
        System.out.println(count);
    }
}

Cela lance une SQLException avec un message comme celui-ci :

Exception in thread "main" java.sql.SQLException: Column not found: total

Tenter d’accéder à un nom de colonne ou à un index qui n’existe pas lance une SQLException, tout comme obtenir des données d’un ResultSet lorsqu’il ne pointe pas vers une ligne valide. Vous devez être capable de reconnaître un tel code. Voici quelques exemples à surveiller. Voyez-vous ce qui ne va pas quand aucune ligne ne correspond ?

var sql = "SELECT * FROM exhibits where name='Pas dans la table'";
try (var ps = conn.prepareStatement(sql);
     var rs = ps.executeQuery()) {
    rs.next();
    rs.getInt(1); // SQLException
}

L’appel à rs.next() fonctionne. Il renvoie false. Cependant, l’appel à un getter par la suite lance une SQLException car le curseur du result set ne pointe pas vers une position valide. Si une correspondance avait été renvoyée, ce code aurait fonctionné. Voyez-vous ce qui ne va pas avec ce qui suit ?

var sql = "SELECT count(*) FROM exhibits";
try (var ps = conn.prepareStatement(sql);
     var rs = ps.executeQuery()) {
    rs.getInt(1); // SQLException
}

Ne pas appeler rs.next() du tout est un problème. Le curseur du result set pointe toujours vers un emplacement avant la première ligne, donc le getter n’a rien vers quoi pointer.

Pour résumer cette section, il est important de se rappeler ce qui suit :

  • Toujours utiliser une instruction if ou une boucle while lors de l’appel à rs.next().
  • Les index de colonnes commencent à 1.

Obtenir des Données pour une Colonne

Il existe de nombreuses méthodes get sur l’interface ResultSet. Le tableau ci-dessous montre les méthodes get que vous devez connaître.

MéthodeType de retour
getBooleanboolean
getDoubledouble
getIntint
getLonglong
getObjectObject
getStringString

Vous remarquerez peut-être que tous les types primitifs ne figurent pas dans le tableau. Il existe des méthodes getByte() et getFloat(), mais vous n’avez pas besoin de les connaître. Il n’y a pas de méthode getChar().

La méthode getObject() peut renvoyer n’importe quel type. Pour un primitif, elle utilise la classe wrapper. Regardons l’exemple suivant :

var sql = "SELECT id, name FROM exhibits";
try (var ps = conn.prepareStatement(sql);
     var rs = ps.executeQuery()) {
     
    while (rs.next()) {
        Object champId = rs.getObject("id");
        Object champNom = rs.getObject("name");
        if (champId instanceof Integer id)
            System.out.println(id);
        if (champNom instanceof String nom)
            System.out.println(nom);
    }
}

Les lignes obtiennent la colonne sous forme d’un Object du type le plus approprié. Les lignes utilisent le pattern matching pour obtenir les types réels. Vous n’utiliserez probablement pas getObject() lorsque vous écrivez du code pour un travail, mais il est bon de le connaître.

Utilisation des Variables de Liaison

Nous avons créé le PreparedStatement et le ResultSet dans la même instruction try-with-resources. Cela ne fonctionne pas si vous avez des variables de liaison car elles doivent être définies entre les deux. Heureusement, nous pouvons imbriquer des try-with-resources pour gérer cela. Ce code affiche l’ID de tous les exhibits correspondant à un nom donné :

var sql = "SELECT id FROM exhibits WHERE name = ?";

try (var ps = conn.prepareStatement(sql)) {
    ps.setString(1, "Zèbre");
    
    try (var rs = ps.executeQuery()) {
        while (rs.next()) {
            int id = rs.getInt("id");
            System.out.println(id);
        }
    }
}

Faites attention au flux ici. D’abord, nous créons le PreparedStatement. Ensuite, nous définissons la variable de liaison. Ce n’est qu’après ces deux opérations que nous avons un try-with-resources imbriqué pour créer le ResultSet.