Bonjour ! Dans la leçon précédente, nous avons appris à exécuter des requêtes de manière sécurisée avec PreparedStatement, nous protégeant ainsi contre les injections SQL. Nous avons vu comment envoyer des données à la base de données. Il est maintenant temps de maîtriser la réception et la manipulation des données qu'elle nous renvoie.
Aujourd'hui, nous allons nous concentrer sur l'objet qui encapsule les résultats d'une requête SELECT : le ResultSet. L'objectif est de devenir capable de parcourir n'importe quel ensemble de résultats, d'en extraire les données de manière fiable, et de les transformer en objets Java utilisables dans notre application. C'est une compétence essentielle pour construire la moindre fonctionnalité qui lit des informations depuis une base de données.
1. Qu'est-ce qu'un ResultSet ?
Lorsqu'on exécute une requête SELECT avec executeQuery(), le pilote JDBC nous retourne un objet de type java.sql.ResultSet. Il est préférable de se le représenter non pas comme une collection (telle qu'une ArrayList), mais plutôt comme une vue tabulaire des données, équipée d'un curseur.
Ce curseur pointe initialement avant la première ligne de résultats. Pour accéder aux données, nous devons déplacer ce curseur ligne par ligne.
Comment récupérer des données avec ResultSet en Java?
Cet article explique très clairement le concept du curseur et le mécanisme d'itération de base.
Lisez la première partie de l'article, "Lecture d’un ResultSet". Concentrez-vous sur le code d'exemple et le schéma qui illustre le mouvement du curseur à chaque appel de rs.next().
L'image ci-dessous résume visuellement le processus : une requête SQL est exécutée, ce qui produit un ResultSet, que nous allons ensuite parcourir pour en extraire les informations.

2. Parcourir les résultats et extraire les données
Le parcours d'un ResultSet se fait presque toujours avec une boucle while. La méthode clé est rs.next(), qui accomplit deux actions :
- Elle avance le curseur à la ligne suivante.
- Elle retourne un
boolean:truesi le curseur se trouve sur une ligne valide (il y a donc des données à lire), etfalses'il a dépassé la dernière ligne.
Une fois que le curseur est positionné sur une ligne, nous pouvons extraire les données de chaque colonne à l'aide des méthodes getXXX(), où XXX est le type de données Java que l'on souhaite obtenir (String, Int, Double, Date, etc.).
Il existe deux manières d'identifier la colonne à lire :
- Par son nom (label) :
rs.getString("login")- Avantages : Très lisible et robuste. Si vous changez l'ordre des colonnes dans votre
SELECT, le code continue de fonctionner. C'est la méthode recommandée.
- Avantages : Très lisible et robuste. Si vous changez l'ordre des colonnes dans votre
- Par son index :
rs.getInt(1)- Avantages : Potentiellement un peu plus performant (la différence est souvent négligeable).
- Inconvénient majeur : L'indexation des colonnes en JDBC commence à 1, et non à 0 comme pour les tableaux ou les listes en Java. C'est une source d'erreurs très fréquente.
La vidéo suivante montre une démonstration complète de ce processus.
TUTO Java JDBC : Prise en main de l'API JDBC
Cette partie de la vidéo de Dominique Liard, que nous avons déjà consultée, met en pratique l'exécution d'une requête SELECT et la lecture du ResultSet retourné.
Regardez attentivement le segment de l'exécution de la requête SELECT. Portez une attention particulière aux points suivants : La structure de la boucle while (rs.next()). L'utilisation des méthodes getInt() et getString() pour récupérer les données. L'exemple qui mélange l'accès par index (getInt(1)) et par nom de colonne (getString("password")). La mise en garde de l'auteur concernant l'indexation qui commence à 1 (le piège de l'indice 1). L'utilisation du try-with-resources pour gérer à la fois le Statement et le ResultSet, garantissant leur fermeture automatique.
Voici un résumé des méthodes getXXX() les plus courantes :
| Méthode | Type de retour Java |
|---|---|
getBoolean() |
boolean |
getDouble() |
double |
getInt() |
int |
getLong() |
long |
getString() |
String |
getDate() |
java.sql.Date |
getTimestamp() |
java.sql.Timestamp |
getObject() |
Object |
3. Cas particulier : un seul résultat attendu
Parfois, une requête est conçue pour ne retourner qu'une seule ligne (par exemple, SELECT * FROM users WHERE id = ?) ou une seule valeur (ex: SELECT COUNT(*) FROM users). Dans ce cas, une boucle while est inappropriée. On utilise plutôt une instruction if.
public int countUsers(Connection connection) throws SQLException {
String sql = "SELECT COUNT(*) FROM users";
int count = 0;
try (Statement stmt = connection.createStatement();
ResultSet rs = stmt.executeQuery(sql)) {
// On vérifie s'il y a bien une ligne de résultat
if (rs.next()) {
// On récupère la valeur de la première (et unique) colonne
count = rs.getInt(1);
}
}
return count;
}
Point crucial : Il est obligatoire d'appeler rs.next() et de s'assurer qu'elle retourne true avant de tenter d'appeler une méthode getXXX(). Si vous essayez de lire des données alors que le curseur n'est pas sur une ligne valide (soit avant la première ligne, soit après la dernière), votre programme lèvera une SQLException.
Comment récupérer des données avec ResultSet en Java?
Cette section de l'article précédent se concentre spécifiquement sur ce cas d'usage.
Lisez la section "Récupération d’une Seule Ligne". Elle illustre bien l'utilisation de if (rs.next()) et explique les SQLException qui se produisent si vous essayez d'accéder aux données au mauvais moment.
4. Mapper un ResultSet à des Objets Java
En pratique, on ne se contente que rarement d'afficher les données dans la console. Le but est de les transformer en objets de notre domaine métier (des "POJOs" - Plain Old Java Objects) pour les utiliser dans le reste de l'application.
Ce processus, appelé mapping, consiste à lire chaque ligne du ResultSet et à utiliser ses valeurs pour instancier et peupler un objet Java.
Voici à quoi ressemble le code pour mapper la table users à une liste d'objets User.
Supposons que nous ayons cette classe User :
public class User {
private int id;
private String login;
private String password;
// Constructeurs, getters, setters et toString()...
}
La méthode de récupération et de mapping ressemblerait à ceci :
public List<User> getAllUsers(Connection connection) throws SQLException {
String sql = "SELECT id, login, password FROM users";
List<User> userList = new ArrayList<>();
try (Statement stmt = connection.createStatement();
ResultSet rs = stmt.executeQuery(sql)) {
while (rs.next()) {
// 1. Extraire les données de la ligne courante
int id = rs.getInt("id");
String login = rs.getString("login");
String password = rs.getString("password");
// 2. Créer un nouvel objet User avec ces données
User user = new User(id, login, password);
// 3. Ajouter l'objet à la liste
userList.add(user);
}
}
return userList;
}
5. Exercice Pratique : Lister tous les utilisateurs
Mettons tout cela en pratique. Vous allez créer une méthode qui récupère tous les utilisateurs de la base de données et les retourne sous forme d'une liste d'objets.
-
Préparez la classe
User:
Créez un fichierUser.javadans votre projet et ajoutez-y une classeUseravec les attributsid(int),login(String), etpassword(String). N'oubliez pas de générer un constructeur, les getters, les setters, et une méthodetoString()pour faciliter l'affichage. -
Créez une classe
UserDAO:
Nous anticipons un peu sur la prochaine leçon en introduisant le pattern DAO (Data Access Object). Créez une classeUserDAO.java. Son rôle sera d'encapsuler toute la logique d'accès aux données pour les utilisateurs. -
Implémentez la méthode
findAll():
Dans votre classeUserDAO, créez la méthodepublic List<User> findAll(Connection connection) throws SQLException.- Votre tâche est d'implémenter cette méthode en suivant le modèle vu dans la section 4.
- Elle doit exécuter une requête
SELECT * FROM users. - Elle doit itérer sur le
ResultSet, créer un objetUserpour chaque ligne, et les ajouter à uneArrayList. - Finalement, elle doit retourner cette liste.
-
Testez dans le
main:
Dans votre méthodemain, après avoir établi la connexion, instanciez votreUserDAO, appelez la méthodefindAll(), et parcourez la liste retournée pour afficher chaque utilisateur.// Dans votre méthode main, à l'intérieur du try-with-resources de la connexion try (Connection connection = DriverManager.getConnection(url, user, password)) { System.out.println("Connexion établie avec succès !"); UserDAO userDAO = new UserDAO(); List<User> users = userDAO.findAll(connection); System.out.println("Liste des utilisateurs :"); for (User user : users) { System.out.println(user); // Fait appel à la méthode toString() de User } } catch (SQLException e) { // ... }
Cliquez ici pour voir une solution possible
User.java
public class User {
private int id;
private String login;
private String password;
public User(int id, String login, String password) {
this.id = id;
this.login = login;
this.password = password;
}
// Getters et Setters...
public int getId() { return id; }
public void setId(int id) { this.id = id; }
public String getLogin() { return login; }
public void setLogin(String login) { this.login = login; }
public String getPassword() { return password; }
public void setPassword(String password) { this.password = password; }
@Override
public String toString() {
return "User{" +
"id=" + id +
", login='" + login + '\'' +
", password='" + password + '\'' +
'}';
}
}
UserDAO.java
import java.sql.*;
import java.util.ArrayList;
import java.util.List;
public class UserDAO {
public List<User> findAll(Connection connection) throws SQLException {
String sql = "SELECT id, login, password FROM users";
List<User> userList = new ArrayList<>();
try (Statement stmt = connection.createStatement();
ResultSet rs = stmt.executeQuery(sql)) {
while (rs.next()) {
int id = rs.getInt("id");
String login = rs.getString("login");
String password = rs.getString("password");
User user = new User(id, login, password);
userList.add(user);
}
}
return userList;
}
}
Conclusion
Félicitations ! Vous savez maintenant comment interroger une base de données et, plus important encore, comment traiter et utiliser les données qu'elle vous retourne.
Les points clés de cette leçon sont :
- Le
ResultSetest un objet avec un curseur qui pointe initialement avant la première ligne. - La boucle
while (rs.next())est le standard pour parcourir plusieurs résultats, tandis queif (rs.next())est utilisé pour un résultat unique. - On accède aux données avec les méthodes
getXXX(), de préférence en utilisant le nom de la colonne pour la lisibilité et la robustesse. - N'oubliez jamais que l'indexation des colonnes commence à 1.
- Le but final est souvent de mapper les lignes du
ResultSetà des objets Java pour les utiliser dans la logique de votre application. ResultSetest une ressource qui doit être fermée, idéalement via un bloctry-with-resources.
Nous avons établi une connexion, exécuté des requêtes sécurisées et lu les résultats. Cependant, notre code d'accès aux données est encore directement dans la classe main ou dans une classe peu structurée. Dans la prochaine leçon, nous allons formaliser l'organisation de ce code en implémentant proprement le pattern DAO (Data Access Object).