Différence entre Statement et PreparedStatement

Haut Java

Je viens d'annoncer le nouveau cours Learn Spring , axé sur les principes de base de Spring 5 et Spring Boot 2:

>> VOIR LE COURS

1. Vue d'ensemble

Dans ce didacticiel, nous explorerons les différences entre les interfaces JDBC's Statement et PreparedStatement . Nous ne couvrirons pas CallableStatement , une interface API JDBC utilisée pour exécuter des procédures stockées.

2. Interface API JDBC

Les deux Déclaration et PreparedStatement peuvent être utilisés pour exécuter des requêtes SQL. Ces interfaces se ressemblent beaucoup. Cependant, ils diffèrent considérablement les uns des autres dans les fonctionnalités et les performances:

  • Instruction - Utilisé pour exécuter des requêtes SQL basées sur des chaînes
  • PreparedStatement - Utilisé pour exécuter des requêtes SQL paramétrées

Pour pouvoir utiliser Statement et PreparedStatement dans nos exemples, nous allons déclarer le connecteur JDBC h2 en tant que dépendance dans notre fichier pom.xml :

 com.h2database h2 1.4.200 

Définissons une entité que nous utiliserons tout au long de cet article:

public class PersonEntity { private int id; private String name; // standard setters and getters }

3. Déclaration

Premièrement, l' interface Statement accepte les chaînes en tant que requêtes SQL. Ainsi, le code devient moins lisible lorsque nous concaténons des chaînes SQL:

public void insert(PersonEntity personEntity) { String query = "INSERT INTO persons(id, name) VALUES(" + personEntity.getId() + ", '" + personEntity.getName() + "')"; Statement statement = connection.createStatement(); statement.executeUpdate(query); }

Deuxièmement, il est vulnérable à l'injection SQL . Les exemples suivants illustrent cette faiblesse.

Dans la première ligne, la mise à jour définira la colonne « nom » de toutes les lignes sur « hacker », car tout ce qui suit «-» est interprété comme un commentaire en SQL et les conditions de l'instruction de mise à jour seront ignorées. Dans la deuxième ligne, l'insertion échouera car le guillemet sur la colonne « nom » n'a pas été échappé:

dao.update(new PersonEntity(1, "hacker' --")); dao.insert(new PersonEntity(1, "O'Brien"))

Troisièmement, JDBC transmet la requête avec des valeurs en ligne à la base de données . Par conséquent, il n'y a pas d'optimisation des requêtes, et surtout, le moteur de base de données doit assurer toutes les vérifications . De plus, la requête n'apparaîtra pas comme la même dans la base de données et empêchera l'utilisation du cache . De même, les mises à jour par lots doivent être exécutées séparément:

public void insert(List personEntities) { for (PersonEntity personEntity: personEntities) { insert(personEntity); } }

Quatrièmement, l' interface Statement convient aux requêtes DDL telles que CREATE, ALTER et DROP :

public void createTables() { String query = "create table if not exists PERSONS (ID INT, NAME VARCHAR(45))"; connection.createStatement().executeUpdate(query); }

Enfin, l' interface Statement ne peut pas être utilisée pour stocker et récupérer des fichiers et des tableaux .

4. Déclaration préparée

Premièrement, PreparedStatement étend l' interface Statement . Il a des méthodes pour lier divers types d'objets , y compris des fichiers et des tableaux. Par conséquent, le code devient facile à comprendre :

public void insert(PersonEntity personEntity) { String query = "INSERT INTO persons(id, name) VALUES( ?, ?)"; PreparedStatement preparedStatement = connection.prepareStatement(query); preparedStatement.setInt(1, personEntity.getId()); preparedStatement.setString(2, personEntity.getName()); preparedStatement.executeUpdate(); }

Deuxièmement, il protège contre l'injection SQL , en échappant le texte de toutes les valeurs de paramètres fournies:

@Test void whenInsertAPersonWithQuoteInText_thenItNeverThrowsAnException() { assertDoesNotThrow(() -> dao.insert(new PersonEntity(1, "O'Brien"))); } @Test void whenAHackerUpdateAPerson_thenItUpdatesTheTargetedPerson() throws SQLException { dao.insert(Arrays.asList(new PersonEntity(1, "john"), new PersonEntity(2, "skeet"))); dao.update(new PersonEntity(1, "hacker' --")); List result = dao.getAll(); assertEquals(Arrays.asList( new PersonEntity(1, "hacker' --"), new PersonEntity(2, "skeet")), result); }

Troisièmement, le PreparedStatement utilise la pré-compilation . Dès que la base de données reçoit une requête, elle vérifie le cache avant de pré-compiler la requête. Par conséquent, s'il n'est pas mis en cache, le moteur de base de données l'enregistrera pour la prochaine utilisation.

De plus, cette fonctionnalité accélère la communication entre la base de données et la JVM via un protocole binaire non SQL. C'est-à-dire qu'il y a moins de données dans les paquets, donc la communication entre les serveurs est plus rapide.

Quatrièmement, PreparedStatement fournit une exécution par lots lors d'une seule connexion à la base de données . Voyons cela en action:

public void insert(List personEntities) throws SQLException { String query = "INSERT INTO persons(id, name) VALUES( ?, ?)"; PreparedStatement preparedStatement = connection.prepareStatement(query); for (PersonEntity personEntity: personEntities) { preparedStatement.setInt(1, personEntity.getId()); preparedStatement.setString(2, personEntity.getName()); preparedStatement.addBatch(); } preparedStatement.executeBatch(); }

Ensuite, PreparedStatement fournit un moyen simple de stocker et de récupérer des fichiers à l'aide des types de données BLOB et CLOB . Dans le même ordre d'idées, il permet de stocker des listes en convertissant java.sql.Array en SQL Array.

Enfin, PreparedStatement implémente des méthodes comme getMetadata () qui contiennent des informations sur le résultat renvoyé.

5. Conclusion

Dans ce didacticiel, nous avons présenté les principales différences entre PreparedStatement et Statement . Les deux interfaces offrent des méthodes pour exécuter des requêtes SQL, mais il est plus approprié d'utiliser Statement pour les requêtes DDL et PreparedStatement pour les requêtes DML.

Comme d'habitude, tous les exemples de code sont disponibles à l'adresse over sur GitHub.

Fond Java

Je viens d'annoncer le nouveau cours Learn Spring , axé sur les principes de base de Spring 5 et Spring Boot 2:

>> VOIR LE COURS