Différence entre Statement et PreparedStatement

222

La déclaration préparée est une version légèrement plus puissante d'une déclaration et doit toujours être au moins aussi rapide et facile à gérer qu'une déclaration.
La déclaration préparée peut être paramétrée

La plupart des bases de données relationnelles gèrent une requête JDBC / SQL en quatre étapes:

  1. Analyser la requête SQL entrante
  2. Compiler la requête SQL
  3. Planifier / optimiser le chemin d'acquisition des données
  4. Exécuter la requête optimisée / acquérir et renvoyer des données

Une instruction effectuera toujours les quatre étapes ci-dessus pour chaque requête SQL envoyée à la base de données. Une instruction préparée pré-exécute les étapes (1) - (3) du processus d'exécution ci-dessus. Ainsi, lors de la création d'une instruction préparée, une pré-optimisation est effectuée immédiatement. L'effet est de réduire la charge sur le moteur de base de données au moment de l'exécution.

Maintenant, ma question est la suivante: "Y a-t-il un autre avantage à utiliser la déclaration préparée?"

CodeBee ..
la source
12
la plus efficace selon moi est que votre requête peut être paramétrée dynamiquement
Hussain Akhtar Wahid 'Ghouri'

Réponses:

198

Avantages d'un PreparedStatement:

  • La précompilation et la mise en cache côté DB de l'instruction SQL permettent une exécution globale plus rapide et la possibilité de réutiliser la même instruction SQL par lots .

  • Prévention automatique des attaques par injection SQL par échappement intégré des guillemets et autres caractères spéciaux. Notez que cela nécessite que vous utilisiez l'une des méthodes pour définir les valeursPreparedStatement setXxx()

    preparedStatement = connection.prepareStatement("INSERT INTO Person (name, email, birthdate, photo) VALUES (?, ?, ?, ?)");
    preparedStatement.setString(1, person.getName());
    preparedStatement.setString(2, person.getEmail());
    preparedStatement.setTimestamp(3, new Timestamp(person.getBirthdate().getTime()));
    preparedStatement.setBinaryStream(4, person.getPhoto());
    preparedStatement.executeUpdate();
    

    et donc ne pas aligner les valeurs dans la chaîne SQL par concaténation de chaîne.

    preparedStatement = connection.prepareStatement("INSERT INTO Person (name, email) VALUES ('" + person.getName() + "', '" + person.getEmail() + "'");
    preparedStatement.executeUpdate();
    
  • Soulage réglage d'objets non standard Java dans une chaîne SQL, par exemple Date, Time, Timestamp, BigDecimal, InputStream( Blob) et Reader( Clob). Sur la plupart de ces types, vous ne pouvez pas "simplement" faire toString()comme vous le feriez dans un simple Statement. Vous pouvez même refactoriser le tout pour l'utiliser PreparedStatement#setObject()dans une boucle, comme illustré dans la méthode utilitaire ci-dessous:

    public static void setValues(PreparedStatement preparedStatement, Object... values) throws SQLException {
        for (int i = 0; i < values.length; i++) {
            preparedStatement.setObject(i + 1, values[i]);
        }
    }
    

    Qui peut être utilisé comme ci-dessous:

    preparedStatement = connection.prepareStatement("INSERT INTO Person (name, email, birthdate, photo) VALUES (?, ?, ?, ?)");
    setValues(preparedStatement, person.getName(), person.getEmail(), new Timestamp(person.getBirthdate().getTime()), person.getPhoto());
    preparedStatement.executeUpdate();
    
BalusC
la source
4
Un texte descriptif et explicatif, couplé à des références et à un exemple, constitue une excellente réponse. +1
XenoRo
1
@RD Cela peut être vrai car une instruction préparée nécessite 2 allers-retours à la base de données: le premier à préparer, le second à exécuter. Cependant, je le testerais. Je suppose que le plan sera toujours mis en cache dans le serveur de base de données pour un Statement, mais cela peut valoir la peine d'être testé.
Brandon
2
Je ne peux pas dire avec certitude avec Java, mais en général, une instruction préparée ne préforme pas "l'échappement intégré des guillemets et autres caractères spéciaux"; au lieu de cela, il effectue la séparation du SQL exécutable et des données , en envoyant les paramètres au SGBD sous forme de paquets d'informations séparés après que le SQL a été converti en plan de requête.
IMSoP
@BalusC - Merci pour l'explication détaillée.
CodeBee ..
49
  1. Ils sont précompilés (une fois), donc plus rapidement pour l'exécution répétée de SQL dynamique (lorsque les paramètres changent)

  2. La mise en cache des instructions de base de données améliore les performances d'exécution de la base de données

    Les bases de données stockent des caches de plans d'exécution pour les instructions précédemment exécutées. Cela permet au moteur de base de données de réutiliser les plans des instructions qui ont été exécutées précédemment. Étant donné que PreparedStatement utilise des paramètres, chaque fois qu'il est exécuté, il apparaît comme le même SQL, la base de données peut réutiliser le plan d'accès précédent, ce qui réduit le traitement. Les instructions "insèrent" les paramètres dans la chaîne SQL et n'apparaissent donc pas comme le même SQL dans la base de données, ce qui empêche l'utilisation du cache.

  3. Le protocole de communication binaire signifie moins de bande passante et des appels de communication plus rapides vers le serveur DB

    Les instructions préparées sont normalement exécutées via un protocole binaire non SQL. Cela signifie qu'il y a moins de données dans les paquets, donc les communications avec le serveur sont plus rapides. En règle générale, les opérations réseau sont un ordre de grandeur plus lent que les opérations sur disque qui sont un ordre de grandeur plus lent que les opérations CPU en mémoire. Par conséquent, toute réduction de la quantité de données envoyées sur le réseau aura un bon effet sur les performances globales.

  4. Ils protègent contre l'injection SQL, en échappant du texte pour toutes les valeurs de paramètre fournies.

  5. Ils offrent une séparation plus forte entre le code de la requête et les valeurs des paramètres (par rapport aux chaînes SQL concaténées), améliorant la lisibilité et aidant les responsables du code à comprendre rapidement les entrées et les sorties de la requête.

  6. En java, peut appeler getMetadata () et getParameterMetadata () pour réfléchir respectivement sur les champs du jeu de résultats et les champs des paramètres

  7. En java, accepte intelligemment les objets java en tant que types de paramètres via setObject, setBoolean, setByte, setDate, setDouble, setDouble, setFloat, setInt, setLong, setShort, setTime, setTimestamp - il se convertit au format de type JDBC qui est compréhensible pour la base de données (pas seulement pour Strings) () format).

  8. En java, accepte les tableaux SQL, comme type de paramètre via la méthode setArray

  9. En java, accepte les CLOBs, BLOBs, OutputStreams et Readers comme "flux" de paramètres via les méthodes setClob / setNClob, setBlob, setBinaryStream, setCharacterStream / setAsciiStream / setNCharacterStream, respectivement

  10. En java, permet de définir des valeurs spécifiques à la base de données pour SQL DATALINK, SQL ROWID, SQL XML et NULL via les méthodes setURL, setRowId, setSQLXML et setNull

  11. En java, hérite de toutes les méthodes de Statement. Il hérite de la méthode addBatch et permet en outre d'ajouter un ensemble de valeurs de paramètres pour correspondre à l'ensemble de commandes SQL par lots via la méthode addBatch.

  12. En java, un type spécial de PreparedStatement (la sous-classe CallableStatement) permet d'exécuter des procédures stockées - prenant en charge les hautes performances, l'encapsulation, la programmation procédurale et SQL, l'administration / la maintenance / le peaufinage de la logique de la base de données et l'utilisation de la logique et des fonctionnalités de la propriété intellectuelle de la base de données

Glen Best
la source
Comment toutes ces merveilles sont-elles possibles alors que les deux ne sont que des interfaces?!?!
Rafael
1
Les «merveilles» sont rendues possibles par des méthodes d'usine standard qui renvoient (spécifiques au fournisseur) des implémentations des interfaces: Connection.createStatement et Connection.prepareStatement. Cette conception vous oblige à travailler contre des interfaces, vous n'avez donc pas besoin de connaître les classes d'implémentation spécifiques et d'éviter un couplage étroit inutile avec de telles classes d'implémentation. Le tout expliqué avec des exemples dans les documents Java jdbc et Java docs. :)
Glen Best
Votre partie "en règle générale" n'a pas de sens n'est-ce pas l'inverse 🤔
bhathiya-perera
38

PreparedStatementest une très bonne défense (mais pas infaillible) pour empêcher les attaques par injection SQL . La liaison des valeurs des paramètres est un bon moyen de se prémunir contre les "petites tables Bobby" qui font une visite indésirable.

duffymo
la source
6
Comment effectuer alors une injection SQL via une instruction préparée?
Michael Borgwardt
2
Michael, les variables passées comme arguments aux instructions préparées seront automatiquement échappées par le pilote JDBC.
CodeBee ..
3
Pouvez-vous donner un exemple de la façon dont une attaque par injection SQL fonctionnerait contre une instruction préparée? Supposez-vous un bogue dans le code de la base de données?
Peter Recore
2
Oui, mais c'est bien au-delà de "assez stupide". C'est stupide stupéfiant. Personne avec une once de connaissances ne ferait cela.
duffymo
2
En outre, de nombreux fournisseurs de bases de données ne prennent pas en charge le paramétrage des noms de colonnes (penser ORDER BY) et / ou des constantes numériques dans certains endroits (penser LIMIT, OFFSETet d' autres solutions de pagination), alors ceux - ci peuvent être attaqués par injection SQL, même lorsque les commandes préparées et le paramétrage est utilisé chaque fois que possible.
dnet
31

Certains des avantages de PreparedStatement sur Statement sont les suivants:

  1. PreparedStatement nous aide à prévenir les attaques par injection SQL car il échappe automatiquement les caractères spéciaux.
  2. PreparedStatement nous permet d'exécuter des requêtes dynamiques avec des entrées de paramètres.
  3. PreparedStatement fournit différents types de méthodes de définition pour définir les paramètres d'entrée de la requête.
  4. PreparedStatement est plus rapide que Statement. Il devient plus visible lorsque nous réutilisons le PreparedStatement ou utilisons ses méthodes de traitement par lots pour exécuter plusieurs requêtes.
  5. PreparedStatement nous aide à écrire du code orienté objet avec des méthodes de définition alors qu'avec Statement, nous devons utiliser la concaténation de chaînes pour créer la requête. S'il y a plusieurs paramètres à définir, l'écriture de Query à l'aide de la concaténation de chaînes semble très moche et sujette aux erreurs.

En savoir plus sur le problème d'injection SQL sur http://www.journaldev.com/2489/jdbc-statement-vs-preparedstatement-sql-injection-example

Pankaj
la source
J'ai lu votre article, vraiment bon. Ma question est maintenant de savoir pourquoi quelqu'un utiliserait Statement?! même pour une requête statique?!
pedram bashiri
J'utilise toujours PreparedStatement, je ne connais aucun scénario spécifique où Statement pourrait avoir plus d'avantages.
Pankaj
13

rien à ajouter,

1 - si vous souhaitez exécuter une requête en boucle (plus d'une fois), l'instruction préparée peut être plus rapide, en raison de l'optimisation que vous avez mentionnée.

2 - la requête paramétrée est un bon moyen d'éviter l'injection SQL. Les requêtes paramétrées ne sont disponibles que dans PreparedStatement.

mhshams
la source
10

La déclaration est statique et la déclaration préparée est dynamique.

La déclaration convient au DDL et le relevé préparé au DML.

L'instruction est plus lente tandis que l'instruction préparée est plus rapide.

plus de différences (archivé)

sandeep vanama
la source
7

Impossible de faire des CLOB dans une déclaration.

Et: (OraclePreparedStatement) ps

orbfish
la source
7

Comme cité par mattjames

L'utilisation d'une instruction dans JDBC doit être 100% localisée pour être utilisée pour DDL (ALTER, CREATE, GRANT, etc.) car ce sont les seuls types d'instructions qui ne peuvent pas accepter les variables BIND. PreparedStatements ou CallableStatements doivent être utilisés pour CHAQUE AUTRE type d'instruction (DML, Queries). Comme ce sont les types d'instructions qui acceptent les variables de liaison.

C'est un fait, une règle, une loi - utilisez des déclarations préparées PARTOUT. Utilisez des déclarations presque nulle part.

Racine
la source
5

l'injection SQL est ignorée par l'instruction préparée, la sécurité est donc augmentée dans l'instruction préparée

geol ashish
la source
4
  • C'est plus facile à lire
  • Vous pouvez facilement faire de la chaîne de requête une constante
nanda
la source
4

L'instruction sera utilisée pour exécuter des instructions SQL statiques et elle ne peut pas accepter les paramètres d'entrée.

PreparedStatement sera utilisé pour exécuter des instructions SQL plusieurs fois de manière dynamique. Il acceptera les paramètres d'entrée.

MARA MP
la source
4

Autre caractéristique de la requête préparée ou paramétrée: référence tirée de cet article.

Cette instruction est l'une des fonctionnalités du système de base de données dans laquelle la même instruction SQL s'exécute de manière répétée avec une grande efficacité. Les instructions préparées sont un type de modèle et sont utilisées par l'application avec différents paramètres.

Le modèle d'instruction est préparé et envoyé au système de base de données et le système de base de données effectue l'analyse, la compilation et l'optimisation sur ce modèle et le stocke sans l'exécuter.

Certains paramètres tels que, où la clause n'est pas transmise lors de la création ultérieure du modèle d'application, envoient ces paramètres au système de base de données et au système de base de données utilisent le modèle de l'instruction SQL et s'exécute selon la demande.

Les instructions préparées sont très utiles contre SQL Injection car l'application peut préparer des paramètres à l'aide de différentes techniques et protocoles.

Lorsque le nombre de données augmente et que les index changent fréquemment à ce moment, les instructions préparées peuvent échouer car dans cette situation, un nouveau plan de requête est nécessaire.

Anvesh
la source
3

Statement l'interface exécute des instructions SQL statiques sans paramètres

PreparedStatement L'interface (instruction d'extension) exécute une instruction SQL précompilée avec / sans paramètres

  1. Efficace pour les exécutions répétées

  2. Il est précompilé donc c'est plus rapide

Bernard
la source
2

Ne vous trompez pas: souvenez-vous simplement

  1. L'instruction est utilisée pour les requêtes statiques comme les DDL, c'est-à-dire créer, supprimer, modifier et prepareStatement est utilisé pour les requêtes dynamiques, c'est-à-dire les requêtes DML.
  2. Dans Statement, la requête n'est pas précompilée tandis que dans prepareStatement, la requête est précompilée, car prepareStatement est efficace en termes de temps.
  3. prepareStatement prend l'argument au moment de la création tandis que Statement ne prend pas d'arguments. Par exemple, si vous souhaitez créer une table et insérer un élément, alors :: Créez une table (statique) en utilisant Statement et insérez un élément (dynamic) en utilisant prepareStatement.
Roopam
la source
1
prepareStatement prend l'argument au moment de la création tandis que Statement ne prend pas d'argument.?
1

J'ai suivi toutes les réponses à cette question pour changer un code hérité fonctionnant en utilisant - Statement(mais ayant des injections SQL) en une solution utilisant PreparedStatementavec un code beaucoup plus lent en raison d'une mauvaise compréhension de la sémantique autour de Statement.addBatch(String sql)& PreparedStatement.addBatch().

Je liste donc mon scénario ici pour que les autres ne commettent pas la même erreur.

Mon scénario était

Statement statement = connection.createStatement();

for (Object object : objectList) {
    //Create a query which would be different for each object 
    // Add this query to statement for batch using - statement.addBatch(query);
}
statement.executeBatch();

Donc, dans le code ci-dessus, j'avais des milliers de requêtes différentes, toutes ajoutées à la même instruction et ce code fonctionnait plus rapidement car les instructions non mises en cache étaient bonnes et ce code était rarement exécuté dans l'application.

Maintenant, pour corriger les injections SQL, j'ai changé ce code en,

List<PreparedStatement> pStatements = new ArrayList<>();    
for (Object object : objectList) {
    //Create a query which would be different for each object 
    PreparedStatement pStatement =connection.prepareStatement(query);
    // This query can't be added to batch because its a different query so I used list. 
    //Set parameter to pStatement using object 
    pStatements.add(pStatement);
}// Object loop
// In place of statement.executeBatch(); , I had to loop around the list & execute each update separately          
for (PreparedStatement ps : pStatements) {
    ps.executeUpdate();
}

Donc, vous voyez, j'ai commencé à créer des milliers d' PreparedStatementobjets et je n'ai finalement pas pu utiliser le traitement par lots parce que mon scénario exigeait cela - il y a des milliers de requêtes UPDATE ou INSERT et toutes ces requêtes se trouvent être différentes.

La fixation de l'injection SQL était obligatoire sans aucun coût de dégradation des performances et je ne pense pas que ce soit possible PreparedStatementdans ce scénario.

De plus, lorsque vous utilisez la fonction de traitement par lots intégrée, vous devez vous soucier de ne fermer qu'une seule instruction, mais avec cette approche de liste, vous devez fermer l'instruction avant de la réutiliser, Réutiliser une instruction préparée

Sabir Khan
la source