Statement s'obtient en appelant la méthode
createStatement() de l'interface
Connection.
Statement.
resultSetType : un entier qui peut prendre les valeurs
TYPE_FORWARD_ONLY,
TYPE_SCROLL_INSENSITIVE et
TYPE_SCROLL_SENSITIVE.
resultSetConcurrency : un entier qui peut prendre les valeurs
CONCUR_READ_ONLY et
CONCUR_UPDATABLE.
resultSetHoldability : de même, un entier qui peut prendre les valeurs
HOLD_CURSORS_OVER_COMMIT et
CLOSE_CURSORS_AT_COMMIT.
ResultSet. Nous verrons leur signification exacte dans le paragraphe sur les
ResultSet.
Il est important de fermer un
Statement lorsqu'il n'est plus utilisé, par appel à sa méthode
close().
Statement. Chacune de ces méthodes doit être utilisée dans son propre contexte.
executeQuery(String) doit être utilisée si l'exécution du
Statement retourne une liste d'objets (cas d'un
select) ;
executeUpdate(String) doit être utilisée si l'exécution du
Statement retourne un nombre d'objets modifiés (cas des requête de création, d'effacement ou de mise à jour) ;
execute(String doit être utilisée si le type de
Statement exécuté n'est pas connu.
ResultSet. Si la requête SQL passée en paramètre ne correspond pas (par exemple, si elle contient un
insert), alors une exception de type
SQLException est jetée.
Exemple 6. Méthode
Statement.executeQuery(String)
Statement smt = connection.createStatement() ; ResultSet rs = smt.executeQuery("select nom, prenom from Marins") ; // exploitation du resultat while (rs.hasNext()) { ... }
Exemple 7. Méthode
Statement.executeUpdate(String)
Statement smt = connection.createStatement() ; int count = smt.executeUpdate("insert into Marins(nom, prenom) " + "values ('Robert', 'Surcouf')") ; // exploitation du resultat if (count > 0) { ... }
true si l'objet retourné est de type
ResultSet et
false s'il s'agit d'un entier. On peut ensuite obtenir ces objets en appelant les méthodes
getResultSet() et
getUpdateCount().
De plus, si l'exécution de cette requête a généré plusieurs résultats, la méthode
getMoreResult() doit être appelée. Voyons l'utilisation de toute ceci.
Exemple 8. Méthode
Statement.execute(String)
Statement stmt = conn.createStatement() ; boolean returnedValue = smt.execute(sql) ; // on ne connaît pas la nature // de cette requête ResultSet rs ; int count ; do { if (returnedValue) { // le résultat est un result set rs = stmt.getResultSet(); // exploitation du result set ... } else { // le résultat est un entier count = stmt.getUpdateCount() ; if (count == -1) { // une valeur -1 indique qu'il n'y a plus de résultat à exploiter break ; // sortie du while } else { // exploitation du count ... } } returnedValue = smt.getMoreResult() ; } while (true) ;
resultSet par appel à la méthode
getResultSet() ferme automatiquement l'ancien. Ce comportement peut être changé, si le pilote utilisé le permet, comme nous le verrons dans le paragraphe sur les
ResultSet.
Statement doivent être fermés par un appel à la méthode
close() une fois qu'ils ne sont plus utilisés. La fermeture d'un
Statement entraîne automatiquement la fermeture de tous les objets
ResultSet qui ont été ouverts sur ce
Statement.
Notons que même si la fermture d'une connexion (par appel à sa méthode
close()) ferme automatiquement tous les
Statement ouverts dessus, il est recommandé de le faire à la main. De même que pour la fermeture des
ResultSet.
Une fois qu'un objet
Statement ou
ResultSet a été fermé, il est illégal d'invoquer une de ses méthodes. L'appel d'une telle méthode peut jeter systématiquement une exception de type
SQLException.
PreparedStatement étend l'interface
Statement, donc tout ce qui précède et qui concerne l'interface
Statement reste valide pour l'interface
PreparedStatement.
L'interface
PreparedStatement ajoute la possibilité de paramétrer des requêtes SQL. Les instances de
PreparedStatement s'utilisent quand une même requête doit être exécutée plusieurs fois, avec des paramètres différents. La chaîne de caractères SQL contient donc des marqueurs, qui seront remplacés par des valeurs à chaque exécution.
On préfèrera systématiquement l'utilisation de
PreparedStatement aux
Statement classiques, en particulier pour les requêtes de mise à jour. Ces requêtes comportent le plus souvent des paramètres, qu'il faut ajouter à la chaîne de caractères SQL à exécuter. Utiliser des
PreparedStatement permet de passer par les méthodes de paramétrage du pilote de base, ce qui nous décharge de la délicate gestion des erreurs, et évitera les attaques par injection de code SQL.
PreparedStatement se crée de la même façon qu'un
Statement normal.
Exemple 9. Création d'un
PreparedStatement
PreparedStatement ps = conn.prepareStatement(
"insert into Marins (nom, prenom, age) values (?, ?, ?)") ;
Statement normal, on peut passer des options lors de la création d'un
PreparedStatement.
On remarque la présence de trois caractères
? dans la chaîne SQL passée en paramètre. Ce sont ces caractères qui doivent être remplacés par des valeurs afin de pouvoir exécuter la requête SQL proprement dite.
PreparedStatement propose un jeu de méthode
set<Type>(int, Type) :
setInt(int, int),
setFloat(int, float),
setString(int, String), etc... Chacune de ces méthodes peut être appelée pour fixer la valeur d'un paramètre donné.
Exemple 10. Fixer les paramètres d'un
PreparedStatement
PreparedStatement ps = conn.prepareStatement(
"insert into Marins (nom, prenom, age) values (?, ?, ?)") ;
ps.setString(1, "Surcouf") ;
ps.setString(2, "Robert") ;
ps.setInt(3, 32) ;
SQLException.
On peut effacer la valeur de tous les paramètres d'un
PreparedStatement en appelant la méthode
clearParameter().
null nécessite l'utilisation d'une méthode particulière :
setNull(int, int). Cette méthode prend deux paramètres :
java.sql.Types
Exemple 11. Positionner un paramètre à
null
PreparedStatement ps = conn.prepareStatement(
"insert into Marins (nom, prenom, age) values (?, ?, ?)") ;
ps.setString(1, "Surcouf") ;
ps.setString(2, "Robert") ;
ps.setNull(3, java.sql.Types.INTEGER) ;
PreparedStatement se déroule de la même manière que pour un
Statement, à l'aide des trois méthodes
executeQuery(),
executeUpdate() et
execute().
PreparedStatement propose deux méthodes :
getMetaData() : retourne un objet
ResultSetMetaData, qui contient des informations sur les colonnes retournées par la requête SQL ;
getParameterMetaData() : retourne un objet
ParameterMetaData, qui contient des informations sur les paramètres déclarés dans ce
PreparedStatement.
getMetaData() retourne un objet de type
ResultSetMetaData qui propose un jeu d'une vingtaine de méthodes pour déterminer le types de colonnes du résultat de la requête. Voyons ceci sur un exemple.
Exemple 12. Utilisation d'un objet
ResultSetMetaData
// on ne connaît pas les colonnes du résultat de cette requête PreparedStatement pstmt = conn.prepareStatement("select * from Marins where id = ?") ; ResultSetMetaData rsmd = pstmt.getMetaData() ; // lecture du nombre de colonne int columnCount = rsmd.geColumnCount() ; for (int i = 1 ; i <= columnCount ; i++) { int columnType = rsmd.getColumnType(i) ; // label de la colonne String columnLabel = rsmd.getColumnLabel(i) ; // nom de la colonne String columnName = rsmd.getColumnName(i) ; }
getParameterMetaData() retourne un objet de type
ParameterMetaData, qui lui-même propose quelques méthodes pour obtenir des informations sur les paramètres d'un
PreparedStatement. Voyons un exemple d'utilisation.
Exemple 13. Utilisation d'un objet
ParameterMetaData
// on ne connaît pas les colonnes du résultat de cette requête PreparedStatement pstmt = conn.prepareStatement("select * from Marins where id = ?") ; ParameterMetaData pmd = pstmt.getParameterMetaData() ; // lecture du nombre de colonne int parameterCount = pmd.geParameterCount() ; for (int i = 1 ; i <= parameterCount ; i++) { // type du paramètre (une constante de java.sql.Types) int parameterType = pmd.getColumnType(i) ; // mode du paramètre int parameterMode = pmd.getParameterMode(i) ; }
IN,
OUT ou
INOUT est utilisé pour les
CallableStatements.
Exemple 14. Méthode
getGeneratedKeys()
Statement stmt = connection.createStatement() ; // le paramètre RETURN_GENERATED_KEYS va nous permettre de récupérer // la clé générée par MySQL int rowCount = smt.executeUpdate( "insert into Marins (nom, prenom) values ('Surcouf', 'Robert')", Statement.RETURN_GENERATED_KEYS) ; ResultSet rs = stmt.getGeneratedKeys(); if (rs.next()) { // récupération de la clé primaire int idSurcouf = rs.getInt(1) ; }
Statement et de les exécuter en une seule fois. On appelle ce genre d'exécution
l'exécution en batch
. En général cela signifie que l'on s'attend à ce que l'exécution totale soit assez longue, et que pendant ce temps, le système peut faire autre chose.
L'exécution de commandes SQL en batch n'est possible que pour des requêtes de modification de la base, celles que l'on exécute avec la méthode
executeUpdate(String). Toute requête SQL qui peut être exécutée de la sorte, peut également être passée en paramètre de la méthode
addBatch(String). L'appel de cette méthode ne fait que stocker la requête SQL, sans l'exécuter. C'est l'appel à la méthode
executeBatch() qui déclenche l'exécution.
L'exécution en batch peut représenter des gains substantiels en performances. Le coût d'une aller et retour avec la base de données est en lui-même coûteux, et l'on tentera le plus possible de minimiser ces allers et retours.
Statement.
Exemple 15. Exécution d'un
Statement en batch
// l'exécution en batch doit se faire en mode non auto-commit connection.setAutoCommit(false) ; Statement smt = connection.createStatement() ; smt.addBatch("insert into Marins(nom, prenom) values ('Surcouf', 'Robert')) ; smt.addBatch("insert into Marins(nom, prenom) values ('Tabarly', 'Eric')) ; ... // lancement de l'exécution de toutes nos insertions int [] updateCounts = smt.executeBatch() ;
executeBatch() d'annuler la transaction et de remettre la base dans l'état initial.
PreparedStatement. Le mécanisme est toutefois légèrement différent, puisqu'un
PreparedStatement est construit sur une unique requête SQL paramétrée.
PreparedStatement propose une méthode
addBatch() qui ne prend aucun paramètre. Au moment où cette méthode est appelée, JDBC construit une requête SQL avec la requête du
PreparedStatement, et le jeu de paramètres qu'il a à sa disposition. Si l'un des paramètres n'a pas été fixé, une erreur est générée. On peut ainsi ajouter autant de requêtes que l'on veut.
L'exécution des requêtes se fait de la même manière et sous les mêmes contraintes que pour un
Statement classique.
Exemple 16. Exécution d'un
PreparedStatement en batch
// l'exécution en batch doit se faire en mode non auto-commit connection.setAutoCommit(false) ; PreparedStatement psmt = connection.prepareStatement("insert into Marins(nom, prenom) " + "values (?, ?)") ; psmt.setParameter(1, "Surcouf") ; psmt.setParameter(2, "Robert") ; // ajout d'une requête avec les paramètres ('Surcouf', 'Robert') psmt.addBatch() ; psmt.setParameter(2, "Pierre") ; // ajout d'une requête avec les paramètres ('Surcouf', 'Pierre') psmt.addBatch() ; psmt.setParameter(1, "Tabarly") ; psmt.setParameter(2, "Eric") ; // ajout d'une requête avec les paramètres ('Tabarly', 'Eric') psmt.addBatch() ; ... // lancement de l'exécution de toutes nos insertions int [] updateCounts = smt.executeBatch() ;