List<String> nomsDeLaLigne(String ligne) throws SQLException {
var sql = "SELECT NOM FROM ARRET WHERE LIGNE = ? ORDER BY NOM";
var resultat = new ArrayList<String>();
try (Connection c = DriverManager.getConnection(URL, USER, PASS);
PreparedStatement ps = c.prepareStatement(sql)) {
ps.setString(1, ligne);
// TODO: exécuter la requête, parcourir le ResultSet, alimenter resultat
}
return resultat;
}TP JDBC — Requêtes, transactions et métadonnées
E1 — La méthode qui manque
Un DAO JDBC se lit en trois temps : ouvrir une connexion, préparer la requête avec des paramètres liés, parcourir le ResultSet. Complétez la méthode ci-dessous en exécutant la requête préparée et en alimentant la liste resultat avec la colonne NOM, en respectant le try-with-resources déjà en place.
System.out.println(nomsDeLaLigne("1"));[]
Résultat attendu une fois le TODO complété : [Bastille, Gare de Lyon, Nation].
E2 — Tuez l’injection
La méthode suivante construit sa requête par concaténation : c’est la faille d’injection SQL classique. Observez d’abord son comportement avec une entrée malveillante, puis écrivez la version sûre nomExact avec un paramètre lié. Comme en E1, la version vide retourne une liste vide par défaut.
List<String> chercherParNomVulnerable(String nom) throws SQLException {
var sql = "SELECT NOM FROM ARRET WHERE NOM = '" + nom + "'";
var resultat = new ArrayList<String>();
try (Connection c = DriverManager.getConnection(URL, USER, PASS);
Statement st = c.createStatement();
ResultSet rs = st.executeQuery(sql)) {
while (rs.next()) resultat.add(rs.getString("NOM"));
}
return resultat;
}List<String> nomExact(String nom) throws SQLException {
var sql = "SELECT NOM FROM ARRET WHERE NOM = ?";
var resultat = new ArrayList<String>();
try (Connection c = DriverManager.getConnection(URL, USER, PASS);
PreparedStatement ps = c.prepareStatement(sql)) {
ps.setString(1, nom);
// TODO: exécuter la requête, parcourir le ResultSet, alimenter resultat
}
return resultat;
}System.out.println(chercherParNomVulnerable("' OR '1'='1").size());
System.out.println(nomExact("' OR '1'='1").size());8
0
Résultat attendu : 8 (tous les arrêts exfiltrés), puis 0 (aucun arrêt ne porte ce nom).
E3 — Tout ou rien
Écrivez un virement atomique entre deux comptes : soit les deux UPDATE sont validés, soit aucun ne l’est. Le compte débité ne doit jamais descendre sous zéro. Le gestionnaire solde est fourni ; complétez transferer en pilotant vous-même le commit.
boolean transferer(String de, String vers, int montant) throws SQLException {
try (Connection c = DriverManager.getConnection(URL, USER, PASS)) {
// TODO: autoCommit=false, deux UPDATE préparés, vérification du solde, commit/rollback
return false;
}
}System.out.println("Avant : courant=" + solde("courant") + " livret=" + solde("livret"));
System.out.println("Virement 1000 : " + transferer("courant", "livret", 1000));
System.out.println("Après : courant=" + solde("courant") + " livret=" + solde("livret"));
transferer("courant", "livret", 999999);
System.out.println("Final : courant=" + solde("courant") + " livret=" + solde("livret"));Avant : courant=5000 livret=3000
Virement 1000 : false
Après : courant=5000 livret=3000
Final : courant=5000 livret=3000
Résultat attendu : 5000/3000, puis virement validé et 4000/4000, puis solde inchangé après le virement impossible.
E4 — Mille inserts, une bourrasque
Insérer 1000 lignes une par une coûte 1000 allers-retours vers le moteur. La mise à jour en lot (addBatch / executeBatch) les regroupe en une seule bourrasque. Chronométrez la version ligne par ligne, puis comparez avec la version en lot fournie.
var debut = System.nanoTime();
try (Connection c = DriverManager.getConnection(URL, USER, PASS);
PreparedStatement ps = c.prepareStatement("INSERT INTO TRAJET(ARRET_ID,HEURE) VALUES(?,?)")) {
for (int i = 0; i < 1000; i++) {
ps.setInt(1, (i % 8) + 1);
ps.setString(2, "12:00");
ps.executeUpdate(); // TODO: remplacer par addBatch() + executeBatch() après la boucle
}
}
System.out.println("Un par un : " + (System.nanoTime() - debut) / 1_000_000 + " ms");Un par un : 205 ms
try (Connection c = DriverManager.getConnection(URL, USER, PASS);
Statement st = c.createStatement()) { st.executeUpdate("DELETE FROM TRAJET"); }
var debutBatch = System.nanoTime();
try (Connection c = DriverManager.getConnection(URL, USER, PASS);
PreparedStatement ps = c.prepareStatement("INSERT INTO TRAJET(ARRET_ID,HEURE) VALUES(?,?)")) {
for (int i = 0; i < 1000; i++) {
ps.setInt(1, (i % 8) + 1);
ps.setString(2, "12:00");
ps.addBatch();
}
ps.executeBatch();
}
System.out.println("Par lot : " + (System.nanoTime() - debutBatch) / 1_000_000 + " ms");Par lot : 85 ms
Lignes dans TRAJET : 1000
Résultat attendu après chaque version : Lignes dans TRAJET : 1000.
E5 — Le ResultSet se raconte
Un ResultSet sait décrire sa propre structure : demandez ses métadonnées colonnes, puis faites reculer son curseur scrollable de la dernière ligne à la première.
try (Connection c = DriverManager.getConnection(URL, USER, PASS);
Statement st = c.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_READ_ONLY);
ResultSet rs = st.executeQuery("SELECT * FROM ARRET ORDER BY NOM")) {
ResultSetMetaData md = rs.getMetaData();
// TODO 1 : pour i de 1 à md.getColumnCount(), afficher nom + type JDBC
// TODO 2 : rs.last(), puis rs.previous() jusqu'à rs.isBeforeFirst(), afficher NOM
}Résultat attendu : ID INTEGER | NOM VARCHAR | LIGNE VARCHAR, puis les 8 noms en ordre alphabétique inverse. Point de discussion : les noms de colonnes apparaissent en MAJUSCULES car H2 v2 stocke les identifiants non quotés en uppercase — JDBC renvoie les filtres tels quels, contrairement à PostgreSQL nativement en minuscules.
Validation
Exécutez toutes les cellules depuis le début. Chaque ligne ci-dessous doit afficher OK.
OK-01 : false
OK-02 : true
OK-03 : false
OK-04 : true
OK-05 : true
Compte rendu
Exportez le notebook rendu. Vous déposez : la sortie de la cellule de vérification de E2 (8 puis 0) accompagnée de trois phrases expliquant pourquoi un paramètre lié stoppe l’injection là où la concaténation ouvre une porte ; ainsi que les lignes de E3 assurant le contrôle transactionnel (setAutoCommit(false), commit(), rollback()), à mettre en évidence dans votre code soumis.
Comment exécuter ce TP
Toutes les cellules de ce notebook sont exécutées, y compris les squelettes : ils compilent tels quels et retournent des valeurs par défaut (liste vide,
false) tant que lesTODOne sont pas complétés. Modifiez les cellules marquées TODO, puis réexécutez tout le notebook depuis le début pour repartir d’un état de base de données propre. La preuve de complétion est le rendu CI : la section Validation doit afficher OK-01 à OK-05. Attention, piège connu de la flotte : l’option//| execute: falseest ignorée pour les cellules{java}— toute cellule du fichier s’exécute réellement au rendu.