W3docs

Java JDBC PreparedStatement

Exécutez du SQL paramétré en toute sécurité avec PreparedStatement pour prévenir les injections SQL en Java.

Un PreparedStatement est un modèle SQL avec des marqueurs ? à la place des valeurs. Vous définissez les valeurs séparément par index, et le pilote envoie le modèle et les données sur des canaux distincts — ainsi, une valeur ne peut jamais être interprétée comme du SQL. C'est la pratique la plus importante dans JDBC : elle rend l'injection structurellement impossible et permet à la base de données de réutiliser le plan de requête. Préférez-le à un Statement simple pour pratiquement tout.

Ce chapitre explique comment créer, lier et exécuter un PreparedStatement, pourquoi il bloque l'injection SQL, comment lier NULL et des valeurs typées, et quand la réutilisation est avantageuse.

Création, liaison, exécution

String sql = "INSERT INTO users (name, age) VALUES (?, ?)";
try (Connection conn = DriverManager.getConnection(url, user, pw);
     PreparedStatement ps = conn.prepareStatement(sql)) {
  ps.setString(1, name);   // bind by 1-based index
  ps.setInt(2, age);
  int rows = ps.executeUpdate();
}

Les marqueurs sont numérotés à partir de 1, pas de 0 — une source constante d'erreurs de décalage. Chaque setXxx correspond au type de la colonne : setString, setInt, setBigDecimal, setTimestamp, et ainsi de suite.

Pourquoi cela bloque l'injection

Avec un Statement, la valeur fait partie du texte SQL, donc un guillemet dans la valeur peut terminer le littéral et injecter de nouvelles commandes. Avec un PreparedStatement, le SQL est fixe et analysé avant que toute valeur soit liée ; la valeur est ensuite transmise comme paramètre typé. Il n'y a pas de chaîne dans laquelle le guillemet d'un attaquant pourrait s'échapper — la valeur dangereuse du chapitre sur Statement devient simplement un nom littéral.

Après avoir exécuté une requête, vous lisez ses lignes avec un ResultSet, exactement comme vous le feriez avec un Statement.

Liaison de NULL et de types spéciaux

Vous ne pouvez pas passer un null Java à setInt (qui prend un primitif), et setString(i, null) est ambigu pour certains types. La forme explicite est setNull(index, sqlType), en nommant le java.sql.Types de la colonne :

ps.setNull(3, java.sql.Types.VARCHAR);

Réutilisation d'un PreparedStatement

Un PreparedStatement est conçu pour être exécuté plusieurs fois avec des valeurs différentes — définir, exécuter, effacer, recommencer. La base de données analyse et planifie le SQL une seule fois et le réutilise, c'est pourquoi les PreparedStatements sont aussi plus rapides dans une boucle que de reconstruire une chaîne Statement à chaque fois. Pour les insertions en masse, combinez cela avec le traitement par lots.

Exemple concret : anatomie d'un modèle et de ses liaisons

Ce programme traite le modèle SQL comme une donnée : il compte les marqueurs, parcourt les valeurs que vous y lieriez (y compris la chaîne malveillante qui avait piraté le Statement), et affiche le code de type setNull — toutes les parties mobiles de la liaison de paramètres, sans base de données en direct.

java— editable, runs on the server

Ce qu'il faut retenir de l'exécution :

  • Le modèle comporte trois marqueurs ?, et le programme les compte — ce nombre correspond exactement au nombre d'appels setXxx à effectuer. Un décalage (lier le paramètre 4 d'une requête à 3 marqueurs) provoque une exception à l'exécution.
  • Les liaisons sont indexées à partir de 1 : le paramètre 1 correspond au premier ?. La boucle affiche bind 1, bind 2, bind 3 pour bien l'illustrer — l'erreur débutant la plus courante est de commencer à 0.
  • La première valeur est la même chaîne x'; DROP TABLE users;-- qui avait détourné le Statement au chapitre précédent. Ici, elle est simplement une donnée liée au paramètre 1 ; le pilote la stocke littéralement comme un nom. L'injection est neutralisée par construction, pas par échappement.
  • Le null au paramètre 3 explique pourquoi setNull(index, Types.VARCHAR) existe. JDBC a besoin du type SQL pour indiquer à la base de données quel genre de NULL c'est — vous le nommez avec une constante java.sql.Types.
  • Chaque valeur porte un type implicite — String, int, NULL-of-VARCHAR — c'est pourquoi il existe un setXxx par type plutôt qu'un unique setter basé sur les chaînes. Faire correspondre le setter au type de colonne est la discipline qui rend les PreparedStatements à la fois sûrs et corrects.

Pratique

Pratique
Pourquoi un PreparedStatement empêche-t-il l'injection SQL même lorsqu'une valeur liée contient un guillemet simple et un point-virgule ?
Pourquoi un PreparedStatement empêche-t-il l'injection SQL même lorsqu'une valeur liée contient un guillemet simple et un point-virgule ?
Was this page helpful?