Nous continuons notre voyage initiatique au pays de JDBC en abordant la manière d'interroger notre BDD. Eh oui, une base de données n'est utile que si nous pouvons consulter, ajouter, modifier et supprimer les données qu'elle contient. Pour y parvenir, il était impératif de se connecter au préalable. Maintenant que c'est chose faite, nous allons voir comment fouiner dans notre BDD.
- Le couple Statement - ResultSet
- Les requêtes préparées
- Modifier des données
- Statement, toujours plus fort
- Gérer les transactions manuellement
Le couple Statement - ResultSet
Voici deux objets que vous utiliserez sûrement beaucoup ! En fait, ce sont ces deux objets qui permettent de récupérer des données de la BDD et de travailler avec celles-ci. Afin de vous faire comprendre tout cela de façon simple, voici un exemple assez complet (mais tout de même pas exhaustif) affichant le contenu de la table classe
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 | //CTRL + SHIFT + O pour générer les imports public class Connect { public static void main(String[] args) { try { Class.forName("org.postgresql.Driver"); String url = "jdbc:postgresql://localhost:5432/Ecole"; String user = "postgres"; String passwd = "postgres"; Connection conn = DriverManager.getConnection(url, user, passwd); //Création d'un objet Statement Statement state = conn.createStatement(); //L'objet ResultSet contient le résultat de la requête SQL ResultSet result = state.executeQuery("SELECT * FROM classe"); //On récupère les MetaData ResultSetMetaData resultMeta = result.getMetaData(); System.out.println("\n**********************************"); //On affiche le nom des colonnes for(int i = 1; i <= resultMeta.getColumnCount(); i++) System.out.print("\t" + resultMeta.getColumnName(i).toUpperCase() + "\t *"); System.out.println("\n**********************************"); while(result.next()){ for(int i = 1; i <= resultMeta.getColumnCount(); i++) System.out.print("\t" + result.getObject(i).toString() + "\t |"); System.out.println("\n---------------------------------"); } result.close(); state.close(); } catch (Exception e) { e.printStackTrace(); } } } |
La figure suivante nous montre le résultat de ce code.
Les metadatas (ou, plus communément, les métadonnées) constituent en réalité un ensemble de données servant à décrire une structure. Dans notre cas, elles permettent de connaître le nom des tables, des champs, leur type…
J'ai simplement exécuté une requête SQL et récupéré les lignes retournées. Mais détaillons un peu plus ce qu'il s'est passé. Déjà, vous avez pu remarquer que j'ai spécifié l'URL complète pour la connexion : sinon, comment savoir à quelle BDD se connecter ?
Ce dernier point mis à part, les choses se sont déroulées en quatre étapes distinctes :
- création de l'objet
Statement
; - exécution de la requête SQL ;
- récupération et affichage des données via l'objet
ResultSet
; - fermeture des objets utilisés (bien que non obligatoire, c'est recommandé).
L'objet Statement
permet d'exécuter des instructions SQL, il interroge la base de données et retourne les résultats. Ensuite, ces résultats sont stockés dans l'objet ResultSet
, grâce auquel on peut parcourir les lignes de résultats et les afficher.
Comme je vous l'ai mentionné, l'objet Statement
permet d'exécuter des requêtes SQL. Ces dernières peuvent être de différents types :
CREATE
;INSERT
;UPDATE
;SELECT
;DELETE
.
L'objet Statement
est fourni par l'objet Connection
grâce à l'instruction conn.createStatement()
. Ce que j'ai fait, ensuite, c'est demander à mon objet Statement
d'exécuter une requête SQL de type SELECT
: SELECT * FROM classe
. Elle demande à la BDD de nous envoyer toutes les classes.
Puisque cette requête retourne un résultat contenant beaucoup de lignes, contenant elles-mêmes plusieurs colonnes, j'ai stocké ce résultat dans un objet ResultSet
, qui permet d'effectuer diverses actions sur des résultats de requêtes SQL.
Ici, j'ai utilisé un objet de type ResultSetMetaData
afin de récupérer les métadonnées de ma requête, c'est-à-dire ses informations globales. J'ai ensuite utilisé cet objet afin de récupérer le nombre de colonnes renvoyé par la requête SQL ainsi que leur nom. Cet objet de métadonnées permet de récupérer des informations très utiles, comme :
- le nombre de colonnes d'un résultat ;
- le nom des colonnes d'un résultat ;
- le type de données stocké dans chaque colonne ;
- le nom de la table à laquelle appartient la colonne (dans le cas d'une jointure de tables) ;
- etc.
Il existe aussi un objet DataBaseMetaData
qui fournit des informations sur la base de données.
Vous comprenez mieux à présent ce que signifie cette portion de code :
1 2 3 4 5 6 | System.out.println("\n**********************************"); //On affiche le nom des colonnes for(int i = 1; i <= resultMeta.getColumnCount(); i++) System.out.print("\t" + resultMeta.getColumnName(i).toUpperCase() + "\t *"); System.out.println("\n**********************************"); |
Je me suis servi de la méthode retournant le nombre de colonnes dans le résultat afin de récupérer le nom de la colonne grâce à son index.
Attention, contrairement aux indices de tableaux, les indices de colonnes SQL commencent à 1 !
Ensuite, je récupère les données de la requête en me servant de l'indice des colonnes :
1 2 3 4 5 6 | while(result.next()){ for(int i = 1; i <= resultMeta.getColumnCount(); i++) System.out.print("\t" + result.getObject(i).toString() + "\t |"); System.out.println("\n---------------------------------"); } |
J'utilise une première boucle me permettant alors de parcourir chaque ligne via la boucle for
tant que l'objet ResultSet
retourne des lignes de résultats. La méthode next()
permet de positionner l'objet sur la ligne suivante de la liste de résultats. Au premier tour de boucle, cette méthode place l'objet sur la première ligne. Si vous n'avez pas positionné l'objet ResultSet
et que vous tentez de lire des données, une exception est levée !
Je suis parti du principe que le type de données de mes colonnes était inconnu, mais étant donné que je les connais, le code suivant aurait tout aussi bien fonctionné :
1 2 3 4 5 | while(result.next()){ System.out.print("\t" + result.getInt("cls_id") + "\t |"); System.out.print("\t" + result.getString("cls_nom") + "\t |"); System.out.println("\n---------------------------------"); } |
Je connais désormais le nom des colonnes retournées par la requête SQL. Je connais également leur type, il me suffit donc d'invoquer la méthode adéquate de l'objet ResultSet
en utilisant le nom de la colonne à récupérer. En revanche, si vous essayez de récupérer le contenu de la colonne cls_nom
avec la méthode getInt("cls_nom")
, vous aurez une exception !
Il existe une méthode getXXX()
par type primitif ainsi que quelques autres correspondant aux types SQL :
getArray(int colummnIndex)
;getAscii(int colummnIndex)
;getBigDecimal(int colummnIndex)
;getBinary(int colummnIndex)
;getBlob(int colummnIndex)
;getBoolean(int colummnIndex)
;getBytes(int colummnIndex)
;getCharacter(int colummnIndex)
;getDate(int colummnIndex)
;getDouble(int colummnIndex)
;getFloat(int colummnIndex)
;getInt(int colummnIndex)
;getLong(int colummnIndex)
;getObject(int colummnIndex)
;getString(int colummnIndex)
.
Pour finir, je n'ai plus qu'à fermer mes objets à l'aide des instructions result.close()
et state.close()
.
Avant de voir plus en détail les possibilités qu'offrent ces objets, nous allons créer deux ou trois requêtes SQL afin de nous habituer à la façon dont tout cela fonctionne.
Entraînons-nous
Le but du jeu est de coder les résultats que j'ai obtenus. Voici, en figure suivante, ce que vous devez récupérer en premier. Je vous laisse chercher dans quelle table nous allons travailler.
Cherchez bien… Bon, vous avez sûrement trouvé, il n'y avait rien de compliqué. Voici une des corrections possibles :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 | //CTRL + SHIFT + O pour générer les imports public class Exo1 { public static void main(String[] args) { try { Class.forName("org.postgresql.Driver"); String url = "jdbc:postgresql://localhost:5432/Ecole"; String user = "postgres"; String passwd = "postgres"; Connection conn = DriverManager.getConnection(url, user, passwd); Statement state = conn.createStatement(); ResultSet result = state.executeQuery("SELECT * FROM professeur"); ResultSetMetaData resultMeta = result.getMetaData(); System.out.println("- Il y a " + resultMeta.getColumnCount() + " colonnes dans cette table"); for(int i = 1; i <= resultMeta.getColumnCount(); i++) System.out.println("\t *" + resultMeta.getColumnName(i)); System.out.println("Voici les noms et prénoms : "); System.out.println("\n---------------------------------"); while(result.next()){ System.out.print("\t" + result.getString("prof_nom") + "\t |"); System.out.print("\t" + result.getString("prof_prenom") + "\t |"); System.out.println("\n---------------------------------"); } result.close(); state.close(); } catch (Exception e) { e.printStackTrace(); } } } |
Allez : on complique la tâche, maintenant ; regardez la figure suivante.
Ne vous faites pas exploser la cervelle tout de suite, on ne fait que commencer ! Voici un code possible afin d'obtenir ce résultat :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 | //CTRL + SHIFT + O pour générer les imports public class Exo2 { public static void main(String[] args) { try { Class.forName("org.postgresql.Driver"); String url = "jdbc:postgresql://localhost:5432/Ecole"; String user = "postgres"; String passwd = "postgres"; Connection conn = DriverManager.getConnection(url, user, passwd); Statement state = conn.createStatement(); String query = "SELECT prof_nom, prof_prenom, mat_nom FROM professeur"; query += " INNER JOIN j_mat_prof ON jmp_prof_k = prof_id"; query += " INNER JOIN matiere ON jmp_mat_k = mat_id ORDER BY prof_nom"; ResultSet result = state.executeQuery(query); String nom = ""; while(result.next()){ if(!nom.equals(result.getString("prof_nom"))){ nom = result.getString("prof_nom"); System.out.println(nom + " " + result.getString("prof_prenom") + " enseigne : "); } System.out.println("\t\t\t - " + result.getString("mat_nom")); } result.close(); state.close(); } catch (Exception e) { e.printStackTrace(); } } } |
Allez, un dernier exemple en figure suivante.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 | //CTRL + SHIFT + O pour générer les imports public class Exo3 { public static void main(String[] args) { try { Class.forName("org.postgresql.Driver"); String url = "jdbc:postgresql://localhost:5432/Ecole"; String user = "postgres"; String passwd = "postgres"; Connection conn = DriverManager.getConnection(url, user, passwd); Statement state = conn.createStatement(); String query = "SELECT prof_nom, prof_prenom, mat_nom, cls_nom FROM professeur"; query += " INNER JOIN j_mat_prof ON jmp_prof_k = prof_id"; query += " INNER JOIN matiere ON jmp_mat_k = mat_id"; query += " INNER JOIN j_cls_jmp ON jcm_jmp_k = jmp_id"; query += " INNER JOIN classe ON jcm_cls_k = cls_id AND cls_id IN(1, 7)"; query += " ORDER BY cls_nom DESC, prof_nom"; ResultSet result = state.executeQuery(query); String nom = ""; String nomClass = ""; while(result.next()){ if(!nomClass.equals(result.getString("cls_nom"))){ nomClass = result.getString("cls_nom"); System.out.println("Classe de " + nomClass + " :"); } if(!nom.equals(result.getString("prof_nom"))){ nom = result.getString("prof_nom"); System.out.println("\t * " + nom + " " + result.getString("prof_prenom") + " enseigne : "); } System.out.println("\t\t\t - " + result.getString("mat_nom")); } result.close(); state.close(); } catch (Exception e) { e.printStackTrace(); } } } |
Statement
Vous avez vu comment obtenir un objet Statement
. Mais je ne vous ai pas tout dit… Vous savez déjà que pour récupérer un objet Statement
, vous devez le demander gentiment à un objet Connection
en invoquant la méthode createStatement()
. Ce que vous ne savez pas, c'est que vous pouvez spécifier des paramètres pour la création de l'objet Statement
. Ces paramètres permettent différentes actions lors du parcours des résultats via l'objet ResultSet
.
Le premier paramètre est utile pour la lecture du jeu d'enregistrements :
TYPE_FORWARD_ONLY
: le résultat n'est consultable qu'en avançant dans les données renvoyées, il est donc impossible de revenir en arrière lors de la lecture ;TYPE_SCROLL_SENSITIVE
: le parcours peut se faire vers l'avant ou vers l'arrière et le curseur peut se positionner n'importe où, mais si des changements surviennent dans la base pendant la lecture, il ne seront pas visibles ;TYPE_SCROLL_INSENSITIVE
: à la différence du précédent, les changements sont directement visibles lors du parcours des résultats.
Le second concerne la possibilité de mise à jour du jeu d'enregistrements :
CONCUR_READONLY
: les données sont consultables en lecture seule, c'est-à-dire que l'on ne peut modifier des valeurs pour mettre la base à jour ;CONCUR_UPDATABLE
: les données sont modifiables ; lors d'une modification, la base est mise à jour.
Par défaut, les ResultSet
issus d'un Statement
sont de type TYPE_FORWARD_ONLY
pour le parcours et CONCUR_READONLY
pour les actions réalisables.
Ces paramètres sont des variables statiques de la classe ResultSet
, vous savez donc comment les utiliser. Voici comment créer un Statement
permettant à l'objet ResultSet
de pouvoir être lu d'avant en arrière avec possibilité de modification :
1 | Statement state = conn.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_UPDATABLE); |
Vous avez appris à créer des Statement
avec des paramètres, mais saviez-vous qu'il existait un autre type de Statement
?
Les requêtes préparées
Il va falloir vous accrocher un tout petit peu… De tels objets sont créés exactement de la même façon que des Statement
classiques, sauf qu'au lieu de cette instruction :
1 | Statement stm = conn.createStatement(); |
… nous devons écrire ceci :
1 | PreparedStatement stm = conn.prepareStatement("SELECT * FROM classe"); |
Jusqu'ici, rien de spécial. Cependant, une différence est déjà effective à ce stade : la requête SQL est précompilée ! Cela a pour effet de réduire le temps d'exécution dans le moteur SQL de la BDD. C'est normal, étant donné qu'il n'aura pas à compiler la requête. En règle générale, on utilise ce genre d'objet pour des requêtes contenant beaucoup de paramètres ou pouvant être exécutées plusieurs fois. Il existe une autre différence de taille entre les objets PreparedStatement
et Statement
: dans le premier, on peut utiliser des paramètres à trous !
En fait, vous pouvez insérer un caractère spécial dans vos requêtes et remplacer ce caractère grâce à des méthodes de l'objet PreparedStatement
en spécifiant sa place et sa valeur (son type étant défini par la méthode utilisée).
Voici un exemple :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 | //CTRL + SHIFT + O pour générer les imports public class Prepare { public static void main(String[] args) { try { Class.forName("org.postgresql.Driver"); String url = "jdbc:postgresql://localhost:5432/Ecole"; String user = "postgres"; String passwd = "postgres"; Connection conn = DriverManager.getConnection(url, user, passwd); Statement state = conn.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_UPDATABLE); //On crée la requête String query = "SELECT prof_nom, prof_prenom FROM professeur"; //Premier trou pour le nom du professeur query += " WHERE prof_nom = ?"; //Deuxième trou pour l'identifiant du professeur query += " OR prof_id = ?"; //On crée l'objet avec la requête en paramètre PreparedStatement prepare = conn.prepareStatement(query); //On remplace le premier trou par le nom du professeur prepare.setString(1, "MAMOU"); //On remplace le deuxième trou par l'identifiant du professeur prepare.setInt(2, 2); //On affiche la requête exécutée System.out.println(prepare.toString()); //On modifie le premier trou prepare.setString(1, "TOTO"); //On affiche à nouveau la requête exécutée System.out.println(prepare.toString()); //On modifie le deuxième trou prepare.setInt(2, 159753); //On affiche une nouvelle fois la requête exécutée System.out.println(prepare.toString()); prepare.close(); state.close(); } catch (Exception e) { e.printStackTrace(); } } } |
Cela nous donne la figure suivante.
Effectivement ; mais quelles méthodes d'affectation de valeur existe-t-il ?
C'est simple : vous vous souvenez de la liste des méthodes de l'objet ResultSet
récupérant des données ? Ici, elle est identique, sauf que l'on trouve setXXX()
à la place de getXXX()
. Tout comme son homologue sans trou, cet objet peut prendre les mêmes types de paramètres pour la lecture et pour la modification des données lues :
1 2 3 4 5 | PreparedStatement prepare = conn.prepareStatement( query, ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_READ_ONLY ); |
Sachez enfin qu'il existe aussi une méthode retournant un objet ResultSetMetaData
: il s'agit de getMetaData()
.
Pour en terminer avec les méthodes de l'objet PreparedStatement
que je présente ici (il en existe d'autres), prepare.clearParameters()
permet de réinitialiser la requête préparée afin de retirer toutes les valeurs renseignées. Si vous ajoutez cette méthode à la fin du code que je vous ai présenté et que vous affichez à nouveau le contenu de l'objet, vous obtenez la figure suivante.
ResultSet : le retour
Maintenant que nous avons vu comment procéder, nous allons apprendre à nous promener dans nos objets ResultSet
. En fait, l'objet ResultSet
offre beaucoup de méthodes permettant d'explorer les résultats, à condition que vous ayez bien préparé l'objet Statement
.
Vous avez la possibilité de :
- vous positionner avant la première ligne de votre résultat :
res.beforeFirst()
; - savoir si vous vous trouvez avant la première ligne :
res.isBeforeFirst()
; - vous placer sur la première ligne de votre résultat :
res.first()
; - savoir si vous vous trouvez sur la première ligne :
res.isFirst()
; - vous retrouver sur la dernière ligne :
res.last()
; - savoir si vous vous trouvez sur la dernière ligne :
res.isLast()
; - vous positionner après la dernière ligne de résultat :
res.afterLast()
; - savoir si vous vous trouvez après la dernière ligne :
res.isAfterLast()
; - aller de la première ligne à la dernière :
res.next()
; - aller de la dernière ligne à la première :
res.previous()
; - vous positionner sur une ligne précise de votre résultat :
res.absolute(5)
; - vous positionner sur une ligne par rapport à votre emplacement actuel :
res.relative(-3)
.
Je vous ai concocté un morceau de code que j'ai commenté et qui met tout cela en oeuvre.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 | //CTRL + SHIFT + O pour générer les imports public class Resultset { public static void main(String[] args) { try { Class.forName("org.postgresql.Driver"); String url = "jdbc:postgresql://localhost:5432/Ecole"; String user = "postgres"; String passwd = "postgres"; Connection conn = DriverManager.getConnection(url, user, passwd); Statement state = conn.createStatement(ResultSet.TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR_UPDATABLE); String query = "SELECT prof_nom, prof_prenom FROM professeur"; ResultSet res = state.executeQuery(query); int i = 1; System.out.println("\n\t---------------------------------------"); System.out.println("\tLECTURE STANDARD."); System.out.println("\t---------------------------------------"); while(res.next()){ System.out.println("\tNom : "+res.getString("prof_nom") +" \t prénom : "+res.getString("prof_prenom")); //On regarde si on se trouve sur la dernière ligne du résultat if(res.isLast()) System.out.println("\t\t* DERNIER RESULTAT !\n"); i++; } //Une fois la lecture terminée, on contrôle si on se trouve bien à l'extérieur des lignes de résultat if(res.isAfterLast()) System.out.println("\tNous venons de terminer !\n"); System.out.println("\t---------------------------------------"); System.out.println("\tLecture en sens contraire."); System.out.println("\t---------------------------------------"); //On se trouve alors à la fin //On peut parcourir le résultat en sens contraire while(res.previous()){ System.out.println("\tNom : "+res.getString("prof_nom") +" \t prénom : "+res.getString("prof_prenom")); //On regarde si on se trouve sur la première ligne du résultat if(res.isFirst()) System.out.println("\t\t* RETOUR AU DEBUT !\n"); } //On regarde si on se trouve avant la première ligne du résultat if(res.isBeforeFirst()) System.out.println("\tNous venons de revenir au début !\n"); System.out.println("\t---------------------------------------"); System.out.println("\tAprès positionnement absolu du curseur à la place N° "+ i/2 + "."); System.out.println("\t---------------------------------------"); //On positionne le curseur sur la ligne i/2 //Peu importe où on se trouve res.absolute(i/2); while(res.next()) System.out.println("\tNom : "+res.getString("prof_nom") +" \t prénom : "+ res.getString("prof_prenom")); System.out.println("\t---------------------------------------"); System.out.println("\tAprès positionnement relatif du curseur à la place N° "+(i-(i-2)) + "."); System.out.println("\t---------------------------------------"); //On place le curseur à la ligne actuelle moins i-2 //Si on n'avait pas mis de signe moins, on aurait avancé de i-2 lignes res.relative(-(i-2)); while(res.next()) System.out.println("\tNom : "+res.getString("prof_nom") +" \t prénom : "+res.getString("prof_prenom")); res.close(); state.close(); } catch (Exception e) { e.printStackTrace(); } } } |
La figure suivante montre le résultat obtenu.
Il est très important de noter l'endroit où vous vous situez dans le parcours de la requête !
Il existe des emplacements particuliers. Par exemple, si vous n'êtes pas encore positionnés sur le premier élément et que vous procédez à un rs.relative(1)
, vous vous retrouvez sur le premier élément. De même, un rs.absolute(0)
correspond à un rs.beforeFirst()
.
Ce qui signifie que lorsque vous souhaitez placer le curseur sur la première ligne, vous devez utiliser absolute(1)
quel que soit l'endroit où vous vous trouvez ! En revanche, cela nécessite que le ResultSet
soit de type TYPE_SCROLL_SENSITIVE
ou TYPE_SCROLL_INSENSITIVE
, sans quoi vous aurez une exception.
Modifier des données
Pendant la lecture, vous pouvez utiliser des méthodes qui ressemblent à celles que je vous ai déjà présentées lors du parcours d'un résultat. Souvenez-vous des méthodes de ce type :
res.getAscii()
;res.getBytes()
;res.getInt()
;res.getString()
;- etc.
Ici, vous devez remplacer getXXX()
par updateXXX()
. Ces méthodes de mise à jour des données prennent deux paramètres :
- le nom de la colonne (
String
) ; - la valeur à attribuer à la place de la valeur existante (cela dépend de la méthode utilisée).
Comment ça, « cela dépend de la méthode utilisée » ?
C'est simple :
updateFloat(String nomColonne, float value)
prend unfloat
en paramètre ;updateString(String nomColonne, String value)
prend une chaîne de caractères en paramètre ;- et ainsi de suite.
Changer la valeur d'un champ est donc très facile. Cependant, il faut, en plus de changer les valeurs, valider ces changements pour qu'ils soient effectifs : cela se fait par la méthode updateRow()
. De la même manière, vous pouvez annuler des changements grâce à la méthode cancelRowUpdates()
. Sachez que si vous devez annuler des modifications, vous devez le faire avant la méthode de validation, sinon l'annulation sera ignorée.
Je vous propose d'étudier un exemple de mise à jour :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 | //CTRL + SHIFT + O pour générer les imports public class Modif { public static void main(String[] args) { try { Class.forName("org.postgresql.Driver"); String url = "jdbc:postgresql://localhost:5432/Ecole"; String user = "postgres"; String passwd = "postgres"; Connection conn = DriverManager.getConnection(url, user, passwd); //On autorise la mise à jour des données //Et la mise à jour de l'affichage Statement state = conn.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_UPDATABLE); //On va chercher une ligne dans la base de données String query = "SELECT prof_id, prof_nom, prof_prenom FROM professeur " + "WHERE prof_nom = 'MAMOU'"; ResultSet res = state.executeQuery(query); res.first(); //On affiche ce que l'on trouve System.out.println("NOM : " + res.getString("prof_nom") + " - PRENOM : " + res.getString("prof_prenom")); //On met à jour les champs res.updateString("prof_nom", "COURTEL"); res.updateString("prof_prenom", "Angelo"); //On valide res.updateRow(); //On affiche les modifications System.out.println("*********************************"); System.out.println("APRES MODIFICATION : "); System.out.println("\tNOM : " + res.getString("prof_nom") + " - PRENOM : " + res.getString("prof_prenom") + "\n"); //On remet les informations de départ res.updateString("prof_nom", "MAMOU"); res.updateString("prof_prenom", "Daniel"); //On valide à nouveau res.updateRow(); //Et voilà ! System.out.println("*********************************"); System.out.println("APRES REMODIFICATION : "); System.out.println("\tNOM : " + res.getString("prof_nom") + " - PRENOM : " + res.getString("prof_prenom") + "\n"); res.close(); state.close(); } catch (Exception e) { e.printStackTrace(); } } } |
La figure suivante représente ce que nous obtenons.
En quelques instants, les données ont été modifiées dans la base de données, nous avons donc réussi à relever le défi !
Nous allons maintenant voir comment exécuter les autres types de requêtes avec Java.
Statement, toujours plus fort
Vous savez depuis quelque temps déjà que ce sont les objets Statement
qui sont chargés d'exécuter les instructions SQL. Par conséquent, vous devez avoir deviné que les requêtes de type INSERT
, UPDATE
, DELETE
et CREATE
sont également exécutées par ces objets. Voici un code d'exemple :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 | //CTRL + SHIFT + O pour générer les imports public class State { public static void main(String[] args) { try { Class.forName("org.postgresql.Driver"); String url = "jdbc:postgresql://localhost:5432/Ecole"; String user = "postgres"; String passwd = "postgres"; Connection conn = DriverManager.getConnection(url, user, passwd); //On autorise la mise à jour des données //Et la mise à jour de l'affichage Statement state = conn.createStatement(ResultSet.TYPE_SCROLL_SENSITIVE,ResultSet.CONCUR_UPDATABLE); PreparedStatement prepare = conn.prepareStatement("UPDATE professeur set prof_prenom = ? "+"WHERE prof_nom = 'MAMOU'"); //On va chercher une ligne dans la base de données String query = "SELECT prof_nom, prof_prenom FROM professeur "+"WHERE prof_nom ='MAMOU'"; //On exécute la requête ResultSet res = state.executeQuery(query); res.first(); //On affiche System.out.println("\n\tDONNEES D'ORIGINE : "); System.out.println("\t-------------------"); System.out.println("\tNOM : " + res.getString("prof_nom") + " - PRENOM : " + res.getString("prof_prenom")); //On paramètre notre requête préparée prepare.setString(1, "Gérard"); //On exécute prepare.executeUpdate(); res = state.executeQuery(query); res.first(); //On affiche à nouveau System.out.println("\n\t\t APRES MAJ : "); System.out.println("\t\t * NOM : " + res.getString("prof_nom") + " - PRENOM :" + res.getString("prof_prenom")); //On effectue une mise à jour prepare.setString(1, "Daniel"); prepare.executeUpdate(); res = state.executeQuery(query); res.first(); //On affiche une nouvelle fois System.out.println("\n\t\t REMISE A ZERO : "); System.out.println("\t\t * NOM : " + res.getString("prof_nom") + " - PRENOM :" + res.getString("prof_prenom")); prepare.close(); res.close(); state.close(); } catch (ClassNotFoundException e) { e.printStackTrace(); } catch (SQLException e) { e.printStackTrace(); } } } |
Cela correspond à la figure suivante.
Ici, nous avons utilisé un PreparedStatement
pour compliquer immédiatement, mais nous aurions tout aussi bien pu utiliser un simple Statement
et invoquer la méthode executeUpdate(String query)
.
Vous savez quoi ? Pour les autres types de requêtes, il suffit d'invoquer la même méthode que pour la mise à jour. En fait, celle-ci retourne un booléen indiquant si le traitement a réussi ou échoué. Voici quelques exemples :
1 2 | state.executeUpdate("INSERT INTO professeur (prof_nom, prof_prenom) VALUES('SALMON', 'Dylan')"); state.executeUpdate("DELETE FROM professeur WHERE prof_nom = 'MAMOU'"); |
Gérer les transactions manuellement
Je ne sais pas si vous êtes au courant, mais certains moteurs SQL comme PostgreSQL vous proposent de gérer vos requêtes SQL grâce à ce que l'on appelle des transactions.
Par où commencer ? Lorsque vous insérez, modifiez ou supprimez des données dans PostgreSQL, il se produit un événement automatique : la validation des modifications par le moteur SQL. C'est aussi simple que ça… Voici un petit schéma en figure suivante pour que vous visualisiez cela.
Lorsque vous exécutez une requête de type INSERT
, CREATE
, UPDATE
ou DELETE
, le type de cette requête modifie les données présentes dans la base. Une fois qu'elle est exécutée, le moteur SQL valide directement ces modifications !
Cependant, vous pouvez avoir la main sur ce point (figure suivante).
Comme cela, c'est vous qui avez le contrôle sur vos données afin de maîtriser l'intégrité de vos données. Imaginez que vous deviez exécuter deux requêtes, une modification et une insertion, et que vous partiez du principe que l'insertion dépend de la mise à jour… Comment feriez-vous si de mauvaises données étaient mises à jour ? L'insertion qui en découle serait mauvaise. Cela, bien sûr, si le moteur SQL valide automatiquement les requêtes exécutées.
Pour gérer manuellement les transactions, on spécifie au moteur SQL de ne pas valider automatiquement les requêtes SQL grâce à une méthode (qui ne concernera toutefois pas l'objet Statement
, mais l'objet Connection
) prenant un booléen en paramètre :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | //CTRL + SHIFT + O pour générer les imports public class Transact { public static void main(String[] args) { try { Class.forName("org.postgresql.Driver"); String url = "jdbc:postgresql://localhost:5432/Ecole"; String user = "postgres"; String passwd = "batterie"; Connection conn = DriverManager.getConnection(url, user, passwd); conn.setAutoCommit(false); } catch (Exception e) { e.printStackTrace(); } } } |
Lorsque vous souhaitez que vos requêtes soient prises en compte, il vous faut les valider en utilisant la méthode conn.commit()
.
En mode setAutoCommit(false)
, si vous ne validez pas vos requêtes, elles ne seront pas prises en compte.
Vous pouvez revenir à tout moment au mode de validation automatique grâce à setAutoCommit(true)
.
Voici un exemple :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | //CTRL + SHIFT + O pour générer les imports public class Transact { public static void main(String[] args) { try { Class.forName("org.postgresql.Driver"); String url = "jdbc:postgresql://localhost:5432/Ecole"; String user = "postgres"; String passwd = "batterie"; Connection conn = DriverManager.getConnection(url, user, passwd); conn.setAutoCommit(false); Statement state = conn.createStatement(ResultSet.TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR_UPDATABLE); String query = "UPDATE professeur SET prof_prenom = 'Cyrille' "+"WHERE prof_nom = 'MAMOU'"; ResultSet result = state.executeQuery("SELECT * FROM professeur"+" WHERE prof_nom = 'MAMOU'"); result.first(); System.out.println("NOM : " + result.getString("prof_nom") + " - PRENOM : " + result.getString("prof_prenom")); state.executeUpdate(query); result = state.executeQuery("SELECT * FROM professeur WHERE prof_nom = 'MAMOU'"); result.first(); System.out.println("NOM : " + result.getString("prof_nom") + " - PRENOM : " + result.getString("prof_prenom")); result.close(); state.close(); } catch (Exception e) { e.printStackTrace(); } } } |
Vous pouvez exécuter ce code autant de fois que vous voulez, vous obtiendrez toujours la même chose que sur la figure suivante.
Vous voyez que malgré sa présence, la requête de mise à jour est inopérante. Vous pouvez voir les modifications lors de l'exécution du script, mais étant donné que vous ne les avez pas validées, elles sont annulées à la fin du code. Pour que la mise à jour soit effective, il faudrait effectuer un conn.commit()
avant la fin du script.
- Les recherches se font via les objets
Statement
etResultSet
. - L'objet
Statement
stocke et exécute la requête SQL. - L'objet
ResultSet
, lui, stocke les lignes résultant de l'exécution de la requête. - Il existe des objets
ResultSetMetaData
etDataBaseMetaData
donnant accès à des informations globales sur une requête (le premier) ou une base de données (pour le second). - Il existe un autre objet qui fonctionne de la même manière que l'objet
ResultSet
, mais qui précompile la requête et permet d'utiliser un système de requête à trous : l'objetPreparedStatement
. - Avec un
ResultSet
autorisant l'édition des lignes, vous pouvez invoquer la méthodeupdateXXX()
suivie de la méthodeupdateRow()
. - Pour la mise à jour, la création ou la suppression de données, vous pouvez utiliser la méthode
executeUpdate(String query)
. - En utilisant les transactions manuelles, toute instruction non validée par la méthode
commit()
de l'objetConnection
est annulée.