Mis à jour le lundi 20 novembre 2017
  • 40 heures
  • Difficile

Ce cours est visible gratuitement en ligne.

Ce cours existe en livre papier.

Vous pouvez obtenir un certificat de réussite à l'issue de ce cours.

Vous pouvez être accompagné et mentoré par un professeur particulier par visioconférence sur ce cours.

J'ai tout compris !

Fouiller dans sa base de données

Connectez-vous ou inscrivez-vous gratuitement pour bénéficier de toutes les fonctionnalités de ce cours !

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

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 :

//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.

Recherche dans la table classe
Recherche dans la table classe

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.

Vous comprenez mieux à présent ce que signifie cette portion de code :

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.

Ensuite, je récupère les données de la requête en me servant de l'indice des colonnes :

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é :

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.

Entraînement à la recherche
Entraînement à la recherche

Cherchez bien… Bon, vous avez sûrement trouvé, il n'y avait rien de compliqué. Voici une des corrections possibles :

//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.

Autre recherche
Autre recherche

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 :

//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.

Dernière ligne droite
Dernière ligne droite
//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ù, et si des changements surviennent dans la base pendant la lecture, ils seront directement visibles lors du parcours des résultats ;

  • TYPE_SCROLL_INSENSITIVE : à la différence du précédent, les changements ne seront pas visibles.

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.

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 :

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 :

Statement stm = conn.createStatement();

… nous devons écrire ceci :

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 :

//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.

Requête préparée
Requête préparée

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 :

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.

Nettoyage des paramètres
Nettoyage des paramètres

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.

//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.

Utilisation d'un ResultSet
Utilisation d'un ResultSet

Il est très important de noter l'endroit où vous vous situez dans le parcours de la requête !

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 un float 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 :

//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.

Mise à jour d'une ligne pendant la lecture
Mise à jour d'une ligne pendant la lecture

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 :

//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.

Mise à jour des données
Mise à jour des données

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 :

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.

Validation automatique d'une transaction
Validation automatique d'une transaction

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).

Gestion manuelle des transactions
Gestion manuelle des transactions

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 :

//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().

Vous pouvez revenir à tout moment au mode de validation automatique grâce à setAutoCommit(true).

Voici un exemple :

//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.

Transaction manuelle
Transaction manuelle

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 et ResultSet.

  • 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 et DataBaseMetaData 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'objet PreparedStatement.

  • Avec un ResultSet autorisant l'édition des lignes, vous pouvez invoquer la méthode updateXXX() suivie de la méthode updateRow().

  • 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'objet Connection est annulée.

Exemple de certificat de réussite
Exemple de certificat de réussite