W3docs

Java JDBC Statement

Exécutez du SQL en Java avec l'interface Statement — quand l'utiliser plutôt que PreparedStatement.

Un Statement envoie une chaîne SQL complète et fixe à la base de données. Vous en créez un à partir d'une Connection, lui passez du SQL et récupérez soit un ResultSet (pour les requêtes), soit un nombre de lignes modifiées (pour les changements). C'est le plus simple des trois types d'instructions JDBC — et celui que vous devriez utiliser le moins, car toute donnée variable dans le SQL doit être concaténée manuellement, ce qui est à l'origine des failles d'injection SQL.

Ce chapitre explique comment créer et exécuter un Statement, les trois méthodes d'exécution et quand chacune s'applique, comment configurer le curseur et lire les clés générées, et — surtout — quand s'arrêter et utiliser un PreparedStatement à la place. Si vous débutez avec JDBC, commencez par l'introduction à JDBC.

Création et exécution

try (Connection conn = DriverManager.getConnection(url, user, pw);
     Statement st = conn.createStatement()) {
  // a query → ResultSet
  try (ResultSet rs = st.executeQuery("SELECT count(*) FROM product")) {
    rs.next();
    System.out.println(rs.getInt(1));
  }
  // a change → update count
  int rows = st.executeUpdate("UPDATE product SET active = true WHERE price > 0");
  System.out.println(rows + " rows updated");
}

Les trois méthodes d'exécution

MéthodeUtilisationRetourne
executeQuery(sql)SELECTun ResultSet
executeUpdate(sql)INSERT / UPDATE / DELETE / DDLint lignes affectées
execute(sql)inconnu / plusieurs résultatsboolean (true si un ResultSet)

Utilisez executeQuery et executeUpdate quand vous savez à l'avance quel type d'instruction vous exécutez — ils retournent directement le bon type. Recourez à execute uniquement dans des outils génériques (une console SQL, un exécuteur de migrations) où le SQL n'est pas connu avant l'exécution ; après cela, appelez getResultSet() ou getUpdateCount() pour récupérer le résultat.

executeUpdate retourne 0 pour les DDL tels que CREATE TABLE, et pour les INSERT/UPDATE/DELETE, il retourne le nombre de lignes affectées — utile pour confirmer qu'une mise à jour a bien correspondu à une ligne.

Configuration du curseur et des clés générées

Lorsque vous créez un statement, vous pouvez choisir le comportement du curseur résultant avec createStatement(resultSetType, resultSetConcurrency) — par exemple TYPE_FORWARD_ONLY, CONCUR_READ_ONLY (valeur par défaut, la plus rapide). Demandez TYPE_SCROLL_INSENSITIVE uniquement si vous avez besoin de parcourir le résultat à rebours, et CONCUR_UPDATABLE uniquement si vous prévoyez de modifier des lignes via le curseur ; les deux coûtent plus cher.

Pour les insertions, passez Statement.RETURN_GENERATED_KEYS puis lisez la clé primaire assignée par la base de données avec getGeneratedKeys() :

try (Statement st = conn.createStatement()) {
  st.executeUpdate(
      "INSERT INTO product(name, price) VALUES ('Widget', 9.99)",
      Statement.RETURN_GENERATED_KEYS);
  try (ResultSet keys = st.getGeneratedKeys()) {
    if (keys.next()) {
      long newId = keys.getLong(1);
      System.out.println("inserted id = " + newId);
    }
  }
}

Sans ce flag, l'appel réussit mais getGeneratedKeys() retourne un ResultSet vide, vous ne pouvez donc pas récupérer le nouvel id.

Quand NE PAS utiliser Statement

Dès qu'une partie du SQL provient d'une variable — un nom d'utilisateur, un id, un terme de recherche — arrêtez-vous et utilisez PreparedStatement à la place. Concaténer des valeurs dans une chaîne Statement est dangereux : une valeur contenant un guillemet peut modifier la signification de la commande. PreparedStatement met également en cache son plan d'analyse, donc une requête exécutée en boucle est plus rapide sous forme de prepared statement. Le chapitre suivant est consacré à cette alternative sécurisée ; pour les procédures stockées, consultez CallableStatement.

Réservez Statement pour du SQL fixe sans valeur variable : configuration de schéma (CREATE TABLE …), DDL ponctuel, ou un SELECT codé en dur sans partie variable.

Avertissement

Ne fermez jamais un Statement tant que vous avez encore besoin de son ResultSet — fermer le statement ferme tout résultat qu'il a produit. Utilisez un bloc try-with-resources, comme dans les exemples ci-dessus, afin que chacun soit fermé dans le bon ordre.

Exemple complet : les constantes de curseur et le piège de l'injection

Ce programme affiche les constantes de configuration ResultSet/Statement que vous passez lors de la création d'un statement, puis démontre concrètement pourquoi le SQL construit par concaténation est dangereux — en montrant ce qu'une valeur malveillante fait au texte de la commande.

java— editable, runs on the server

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

  • Les constantes de curseur sont de simples int que vous passez à createStatement. TYPE_FORWARD_ONLY + CONCUR_READ_ONLY est la valeur par défaut et la moins coûteuse ; vous ne demandez un curseur défilant ou modifiable que si vous en avez réellement besoin.
  • Statement.RETURN_GENERATED_KEYS est le flag qui permet à un INSERT de vous retourner le nouvel id auto-incrémenté via getGeneratedKeys() — sans lui, vous ne pouvez pas récupérer la clé assignée par la base de données.
  • La première requête concaténée est inoffensive car Acme ne contient pas de métacaractères SQL. C'est exactement pourquoi la concaténation de chaînes semble fonctionner lors des tests — puis casse en production avec des données réelles.
  • La deuxième valeur contient un guillemet et un point-virgule, de sorte que le SELECT unique prévu devient un SELECT suivi d'un DROP TABLE. Les données ont échappé à leurs guillemets et sont devenues du SQL exécutable — la définition classique de l'injection.
  • La solution n'est jamais « échapper les guillemets vous-même ». C'est de cesser complètement de construire du SQL à partir de valeurs et de laisser PreparedStatement envoyer le modèle et les données séparément — le sujet du prochain chapitre.

Exercice

Pratique
Votre code construit une requête en concaténant directement une valeur de formulaire web dans la chaîne SQL après WHERE owner =. Quelle est la correction appropriée ?
Votre code construit une requête en concaténant directement une valeur de formulaire web dans la chaîne SQL après WHERE owner =. Quelle est la correction appropriée ?
Was this page helpful?