Mon site perso a changé d'adresse.
Vous allez être automatiquement redirigé
vers cette dernière dans quelques secondes ...

http://www.jmdoudoux.fr/

Merci de bien vouloir mettre à jour vos favoris.


Si rien ne se passe cliquez sur le lien ci-dessus.

  21. L'interaction avec le réseau 23. La gestion dynamique des objets et l'introspection Imprimer Sommaire Consulter avec table des matières Développons en Java   v 0.85 béta  
Copyright (C) 1999-2005 Jean-Michel DOUDOUX  

 

22. L'accès aux bases de données : JDBC

 

chapitre 2 2

 

JDBC est l'acronyme de Java DataBase Connectivity et désigne une API définie par Sun pour permettre un accès aux bases de données avec Java.

Ce chapitre présente dans plusieurs sections l'utilisation de cette API :

 

22.1. Les outils nécessaires pour utiliser JDBC

Les classes de JDBC version 1.0 sont regroupées dans le package java.sql et sont incluses dans le JDK à partir de sa version 1.1. La version 2.0 de cette API est incluse dans la version 1.2 du JDK.

Pour pouvoir utiliser JDBC, il faut un pilote qui est spécifique à la base de données à laquelle on veut accéder. Avec le JDK, Sun fournit un pilote qui permet l'accès aux bases de données via ODBC.

Ce pilote permet de réaliser l'indépendance de JDBC vis à vis des bases de données.

Pour utiliser le pont JDBC-ODBC sous Window 9x, il faut utiliser ODBC en version 32 bits.

 

22.2. Les types de pilotes JDBC

Il existe quatre types de pilote JDBC :

  1. Type 1 ( JDBC-ODBC bridge ) : le pont JDBC-ODBC qui s'utilise avec ODBC et un pilote ODBC spécifique pour la base à accéder. Cette solution fonctionne très bien sous Windows. C'est la solution idéale pour des développements avec exécution sous Windows d'une application locale. Cette solution « simple » pour le développement possède plusieurs inconvenients :

    • la multiplication du nombre de couches rend complexe l'architecture (bien que transparent pour le développeur) et détériore un peu les performances
    • lors du deploiement, ODBC et son pilote doivent être installé sur tous les postes ou l'application va fonctionner.
    • la partie native (ODBC et son pilote) rend l'application moins portable et dépendant d'une plateforme.


  2. Type 2 : un driver écrit en java qui appelle l'API native de la base de données

    Ce type de driver convertit les ordres JDBC pour appeler directement les API de la base de données via un pilote natif sur le client. Ce type de driver nécessite aussi l'utilisation de code natif sur le client.



  3. Type 3 : un driver écrit en Java utilisant le protocole natif de la base de données

    Ce type de driver utilise un protocole réseau propriétaire spécifique à une base de données. Un serveur dédié reçoit les messages par ce protocole et dialogue directement avec la base de données. Ce type de driver peut être facilement utilisé par une applet mais dans ce cas le serveur intermédiaire doit obligatoirement être installé sur la machine contenant le serveur web.



  4. Type 4 : un driver Java natif

    Ce type de driver, écrit en java, appelle directement le SGBD par le réseau. Ils sont fournis par l'éditeur de la base de données.

Les drivers se présentent souvent sous forme de fichiers jar dont le chemin doit être ajouté au classpath pour permettre au programme de l'utiliser.

 

22.3. Enregistrer une base de données dans ODBC sous Windows 9x ou XP

Pour utiliser un pilote de type 1 (pont ODBC-JDBC) sous Windows 9x, il est nécessaire d'enregistrer la base de données dans ODBC avant de pouvoir l'utiliser.

 

stop Attention : ODBC n'est pas fourni en standard avec Windows 9x.

Pour enregistrer une nouvelle base de données, il faut utiliser l'administrateur de source de données ODBC.

Pour lancer cette application sous Windows 9x, il faut doubler cliquer sur l'icône "ODBC 32bits" dans le panneau de configuration.

Sous Windows XP, il faut double cliquer sur l'icône "Source de données (ODBC)" dans le répertoire "Outils d'administration" du panneau de configuration.

L'outil se compose de plusieurs onglets.

L'onglet "Pilote ODBC" liste l'ensemble des pilotes qui sont installés sur la machine.

L'onglet "Source de données utilisateur" liste l'ensemble des sources de données pour l'utilisateur courament connecté sous Windows.

L'onglet "Source de données système" liste l'ensemble des sources de données accessibles par tous les utilisateurs.

Le plus simple est de créer une telle source de données en cliquant sur le bouton "Ajouter". Une boite de dialogue permet de sélectionner le pilote qui sera utilisé par la source de donées.

Il suffit de sélectionner le pilote et de cliquer sur "Terminer". Dans l'exemple ci dessous, le pilote sélectionné concerne une base Microsoft Access.

Il suffit de saisir les informations nécessaires notamment le nom de la source de données et de sélectionner la base. Un clic sur le bouton "Ok" crée la source de données qui pourra alors être utilisée.

 

22.4. Présentation des classes de l'API JDBC

Toutes les classes de JDBC sont dans le package java.sql. Il faut donc l'importer dans tous les programmes devant utiliser JDBC.

Exemple ( code Java 1.1 ) :
import java.sql.*;

Il y a 4 classes importantes : DriverManager, Connection, Statement ( et PreparedStatement ), et ResultSet, chacune correspondant à une étape de l'accès aux données :

Classe Role
DriverManager charge et configure le driver de la base de données.
Connection réalise la connection et l'authentification à la base de données.
Statement ( et PreparedStatement ) contient la requête SQL et la transmet à la base de données.
ResultSet permet de parcourir les informations retournées par la base de données dans le cas d'une sélection de données

Chacunes de ces classes dépend de l'instanciation d'un objet de la précédente classe.

 

22.5. La connexion à une base de données

 

22.5.1. Le chargement du pilote

Pour se connecter à une base de données via ODBC, il faut tout d'abord charger le pilote JDBC-ODBC qui fait le lien entre les deux.

Exemple ( code Java 1.1 ) :
Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");

Pour se connecter à une base en utilisant un driver spécifique, la documentation du driver fournit le nom de la classe à utiliser. Par exemple, si le nom de la classe est jdbc.DriverXXX, le chargement du driver se fera avec le code suivant :

Class.forName("jdbc.DriverXXX");

Exemple ( code Java 1.1 ) : Chargement du pilote pour un base PostgreSQL sous Linux
Class.forName("postgresql.Driver");

Il n'est pas nécessaire de créer une instance de cette classe et de l'enregistrer avec le DriverManager car l'appel à Class.forName le fait automatiquement : ce traitement charge le pilote et créer une instance de cette classe.

La méthode static forName() de la classe Class peut lever l'exception java.lang.ClassNotFoundException.

 

22.5.2. L'établissement de la connection

Pour se connecter à une base de données, il faut instancier un objet de la classe Connection en lui précisant sous forme d'URL la base à accéder.

Exemple ( code Java 1.1 ) : Etablir une connexion sur la base testDB via ODBC
String DBurl = "jdbc:odbc:testDB";

con = DriverManager.getConnection(DBurl);

La syntaxe URL peut varier d'un type de base de données à l'autre mais elle est toujours de la forme : protocole:sous_protocole:nom

« jbdc » désigne le protocole est vaut toujours « jdbc ». « odbc » désigne le sous protocole qui définit le mécanisme de connection pour un type de bases de données.

Le nom de la base de données doit être celui saisi dans le nom de la source sous ODBC.

La méthode getConnection() peut lever une exception de la classe java.sql.SQLException.

Le code suivant décrit la création d'une connection avec un user et un mot de passe :

Exemple ( code Java 1.1 ) :
Connection con = DriverManager.getConnection(url, "myLogin", "myPassword");

A la place de " myLogin " ; il faut mettre le nom du user qui se connecte à la base et mettre son mot de passe à la place de "myPassword "

Exemple ( code Java 1.1 ) :
String url = "jdbc:odbc:factures";

Connection con = DriverManager.getConnection(url, "toto", "passwd");

La documentation d'un autre driver indiquera le sous protocole à utiliser ( le protocole à mettre derrière jdbc dans l'URL).

Exemple ( code Java 1.1 ) : Connection à la base PostgreSQL nommée test avec le user jumbo et le mot de passe 12345 sur la machine locale
Connection con=DriverManager.getConnection("jdbc:postgresql://localhost/test","jumbo","12345");

 

22.6. Accéder à la base de données

Une fois la connection établie, il est possible d'exécuter des ordres SQL. Les objets qui peuvent être utilisés pour obtenir des informations sur la base de données sont :

Classe Role
DatabaseMetaData informations à propos de la base de données : nom des tables, index, version ...
ResultSet résultat d'une requête et information sur une table. L'accès se fait enregistrement par enregistrement.
ResultSetMetaData informations sur les colonnes (nom et type) d'un ResultSet

 

22.6.1. L'execution de requêtes SQL

Les requêtes d'interrogation SQL sont exécutées avec les méthodes d'un objet Statement que l'on obtient à partir d'un objet Connection

Exemple ( code Java 1.1 ) :
ResultSet résultats = null;
String requete = "SELECT * FROM client";

try {
   Statement stmt = con.createStatement();
   résultats = stmt.executeQuery(requete);
} catch (SQLException e) {
   //traitement de l'exception
}

Un objet de la classe Statement permet d'envoyer des requetes SQL à la base. Le création d'un objet Statement s'effectue à partir d'une instance de la classe Connection :

Exemple ( code Java 1.1 ) :
Statement stmt = con.createStatement();

Pour une requête de type interrogation (SELECT), la méthode à utiliser de la classe Statement est exécuteQuery. Pour des traitements de mise à jour, il faut utiliser la méthode executeUpdate(). Lors de l'appel à la méthode d'exécution, il est nécessaire de lui fournir en paramètre la requête SQL sous forme de chaine.

Le résultat d'une requête d'intérrogation est renvoyé dans un objet de la classe ResultSet par la méthode executeQuery().

Exemple ( code Java 1.1 ) :
ResultSet rs = stmt.executeQuery("SELECT * FROM employe");

La méthode executeUpdate() retourne le nombre d'enregistrements qui ont été mis à jour

Exemple ( code Java 1.1 ) :
...

//insertion d'un enregistrement dans la table client

requete = "INSERT INTO client VALUES (3,'client 3','prenom 3')";
try {
   Statement stmt = con.createStatement();
   int nbMaj = stmt.executeUpdate(requete);
   affiche("nb mise a jour = "+nbMaj);
} catch (SQLException e) {
   e.printStackTrace();
}

...

Lorsque la méthode executeUpdate() est utilisée pour exécuter un traitement de type DDL ( Data Defiition Langage : définition de données ) comme la création d'un table, elle retourne 0. Si la méthode retourne 0, cela peut signifier deux choses : le traitement de mise à jour n'a affecté aucun enregistrement ou le traitement concernait un traitement de type DDL.

Si l'on utilise executeQuery() pour exécuter une requête SQL ne contenant pas d'ordre SELECT, alors une exception de type SQLException est levée.

Exemple ( code Java 1.1 ) :
...

requete = "INSERT INTO client VALUES (4,'client 4','prenom 4')";
try {
   Statement stmt = con.createStatement();
   ResultSet résultats = stmt.executeQuery(requete);
} catch (SQLException e) {
   e.printStackTrace();
}

...

résultat :
java.sql.SQLException: No ResultSet was produced
java.lang.Throwable(java.lang.String)
java.lang.Exception(java.lang.String)
java.sql.SQLException(java.lang.String)
java.sql.ResultSet sun.jdbc.odbc.JdbcOdbcStatement.executeQuery(java.lang.String)
void testjdbc.TestJDBC1.main(java.lang.String [])

stop Attention : dans ce cas la requête est quand même effectuée. Dans l'exemple, un nouvel enregistrement est créé dans la table.

Il n'est pas nécessaire de définir un objet Statement pour chaque ordre SQL : il est possible d'un définir un et de le réutiliser

 

22.6.2. La classe ResultSet

C'est une classe qui représente une abstraction d'une table qui se compose de plusieurs enregistrements constitués de colonnes qui contiennent les données.

Les principales méthodes pour obtenir des données sont :

Méthode Role
getInt(int) retourne le contenu de la colonne dont le numéro est passé en paramètre sous forme d'entier.
getInt(String) retourne le contenu de la colonne dont le nom est passé en paramètre sous forme d'entier.
getFloat(int) retourne le contenu de la colonne dont le numéro est passé en paramètre sous forme de nombre flottant.
getFloat(String)  
getDate(int) retourne le contenu de la colonne dont le numéro est passé en paramètre sous forme de date.
getDate(String)  
next() se déplace sur le prochain enregistrement : retourne false si la fin est atteinte
Close() ferme le ResultSet
getMetaData() retourne un objet ResultSetMetaData associé au ResultSet.

La méthode getMetaData() retourne un objet de la classe ResultSetMetaData qui permet d'obtenir des informations sur le résultat de la requête. Ainsi, le nombre de colonne peut être obtenu grace à la méthode getColumnCount de cet objet.

Exemple ( code Java 1.1 ) :
ResultSetMetaData rsmd;
rsmd = results.getMetaData();
nbCols = rsmd.getColumnCount();

La méthode next() déplace le curseur sur le prochain enregistrement. Le curseur pointe initialement juste avant le premier enregistrement : il est nécessaire de faire un premier appel à la méthode next() pour se placer sur le premier enregistrement.

Des appels successifs à next permettent de parcourir l'ensemble des enregistrements.

Elle retourne false lorsqu'il n'y a plus d'enregistrement. Il faut toujours protéger le parcours d'une table dans un bloc de capture d'exception

Exemple ( code Java 1.1 ) :
//parcours des données retournées

try {
   ResultSetMetaData rsmd = résultats.getMetaData();
   int nbCols = rsmd.getColumnCount();
   boolean encore = résultats.next();
   while (encore) {
      for (int i = 1; i <= nbCols; i++)
         System.out.print(résultats.getString(i) + " ");
      System.out.println();
      encore = résultats.next();
   }
   résultats.close();
} catch (SQLException e) {
   //traitement de l'exception
}

Les méthodes getXXX() permettent d'extraire les données selon leur type spécifiée par XXX tel que getString(), getDouble(), getInteger(), etc ... . Il existe deux formes de ces méthodes : indiquer le numéro la colonne en paramètre (en commençant par 1) ou indiquer le nom de la colonne en paramètre. La première méthode est plus efficace mais peut générer plus d'erreurs à l'éxecution notamment si la structure de la table évolue.

stop Attention : il est important de noter que ce numéro de colonne fourni en paramètre fait référence au numéro de colonne de l'objet resultSet ( celui correspondant dans l'ordre SELECT )et non au numéro de colonne de la table.

La méthode getString() permet d'obtenir la valeur d'un champ de n'importe quel type.

 

22.6.3. Exemple complet de mise à jour et de sélection sur une table

Exemple ( code Java 1.1 ) :
import java.sql.*;

public class TestJDBC1 {

   private static void affiche(String message) {
      System.out.println(message);
   }

   private static void arret(String message) {
      System.err.println(message);
      System.exit(99);
   }

   public static void main(java.lang.String[] args) {
      Connection con = null;
      ResultSet résultats = null;
      String requete = "";

      // chargement du pilote
      try {
         Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
      } catch (ClassNotFoundException e) {
         arret("Impossible de charger le pilote jdbc:odbc");
      }

      //connection a la base de données

      affiche("connection a la base de données");
      try {

         String DBurl = "jdbc:odbc:testDB";
         con = DriverManager.getConnection(DBurl);
      } catch (SQLException e) {
         arret("Connection à la base de données impossible");
      }

      //insertion d'un enregistrement dans la table client 
      affiche("creation enregistrement");

      requete = "INSERT INTO client VALUES (3,'client 3','client 4')";
      try {
         Statement stmt = con.createStatement();
         int nbMaj = stmt.executeUpdate(requete);
         affiche("nb mise a jour = "+nbMaj);
      } catch (SQLException e) {
          e.printStackTrace();
      }

      //creation et execution de la requete
      affiche("creation et execution de la requête");
      requete = "SELECT * FROM client";

      try {
         Statement stmt = con.createStatement();
         résultats = stmt.executeQuery(requete);
      } catch (SQLException e) {
         arret("Anomalie lors de l'execution de la requête");
      }

      //parcours des données retournées
      affiche("parcours des données retournées");
      try {
         ResultSetMetaData rsmd = résultats.getMetaData();
         int nbCols = rsmd.getColumnCount();
         boolean encore = résultats.next();

         while (encore) {

            for (int i = 1; i <= nbCols; i++)
               System.out.print(résultats.getString(i) + " ");
            System.out.println();
            encore = résultats.next();
         }

         résultats.close();
      } catch (SQLException e) {
         arret(e.getMessage());
      }

      affiche("fin du programme");
      System.exit(0);
   }
}

résultat :
connection a la base de données
creation enregistrement
nb mise a jour = 1
creation et execution de la requête
parcours des données retournées
1.0 client 1 prenom 1 
2.0 client 2 prenom 2 
3.0 client 3 client 4 
fin du programme

 

22.7. Obtenir des informations sur la base de données

 

22.7.1. La classe ResultSetMetaData

La méthode getMetaData() d'un objet ResultSet retourne un objet de typeResultSetMetaData. Cet objet permet de connaître le nombre, le nom et le type des colonnes.

Méthode Role
int getColumnCount() retourne le nombre de colonnes du ResultSet
String getColumnName(int) retourne le nom de la colonne dont le numéro est donné
String getColumnLabel(int) retourne le libellé de la colonne donnée
boolean isCurrency(int) retourne true si la colonne contient un nombre au format monétaire
boolean isReadOnly(int) retourne true si la colonne est en lecture seule
boolean isAutoIncrement(int) retourne true si la colonne est auto incrémentée
int getColumnType(int) retourne le type de données SQL de la colonne

 

22.7.2. La classe DatabaseMetaData

Un objet de la classe DatabaseMetaData permet d'obtenir des informations sur la base de données dans son ensemble : nom des tables, nom des colonnes dans une table, méthodes SQL supportées

Méthode Role
ResultSet getCatalogs() retourne la liste du catalogue d'informations ( Avec le pont JDBC-ODBC, on obtient la liste des bases de données enregistrées dans ODBC).
ResultSet getTables(catalog, schema, tableNames, columnNames) retourne une description de toutes les tables correspondant au TableNames donné et à toutes les colonnes correspondantes à columnNames.
ResultSet getColumns(catalog, schema, tableNames, columnNames) retourne une description de toutes les colonnes correspondantes au TableNames donné et à toutes les colonnes correspondantes à columnNames.
String getURL() retourne l'URL de la base à laquelle on est connecté
String getDriverName() retourne le nom du driver utilisé

La méthode getTables() de l'objet DataBaseMetaData demande quatre arguments :

getTables(catalog, schema, tablemask, types[]);

Exemple ( code Java 1.1 ) :
con = DriverManager.getConnection(url);
dma =con.getMetaData();
String[] types = new String[1];
types[0] = "TABLES"; //set table type mask

results = dma.getTables(null, null, "%", types);

boolean more = results.next();
while (more) {
   for (i = 1; i <= numCols; i++)
      System.out.print(results.getString(i)+" ");
   System.out.println();
   more = results.next();
}

 

22.8. L'utilisation d'un objet PreparedStatement

L'interface PreparedStatement définit les méthodes pour un objet qui va encapsuler une requête pré-compilée.  Ce type de requête est particulièrement adapté pour une exécution répétée d'une même requête avec des paramètres différents.

Cette interface hérite de l'interface Statement.

Lors de l'utilisation d'un objet de type PreparedStatement, la requête est envoyée au moteur de la base de données pour que celui ci prépare son exécution.

Un  objet qui implémente l'interface PreparedStatement est obtenu en utilisant la méthode preparedStatement() d'un objet de type Connection. Cette méthode attend en paramètre une chaîne de caractères contenant la requête SQL. Dans cette chaine, chaque paramètre est représenté par un caractère ?.

Un ensemble de méthode setXXX() (ou XXX représente un type primitif ou certains objets tel que String, Date, Object, ...) permet de fournir les valeurs de chaque paramètre défini dans la requête. Le premier paramètre de ces méthodes précise le numéro du paramètre dont la méthode va fournir la valeur. Le second paramètre précise cette valeur.

Exemple ( code Java 1.1 ) :
package com.jmd.test.dej;

import java.sql.*;

public class TestJDBC2 {

  private static void affiche(String message) {
    System.out.println(message);
  }

  private static void arret(String message) {
    System.err.println(message);
    System.exit(99);
  }

  public static void main(java.lang.String[] args) {
    Connection con = null;
    ResultSet resultats = null;
    String requete = "";

    try {
      Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
    } catch (ClassNotFoundException e) {
      arret("Impossible de charger le pilote jdbc:odbc");
    }

    affiche("connection a la base de données");
    try {

      String DBurl = "jdbc:odbc:testDB";
      con = DriverManager.getConnection(DBurl);
      PreparedStatement recherchePersonne = 
        con.prepareStatement("SELECT * FROM personnes WHERE nom_personne = ?");

      recherchePersonne.setString(1, "nom3");

      resultats = recherchePersonne.executeQuery();

      affiche("parcours des données retournées");

      boolean encore = resultats.next();

      while (encore) {
        System.out.print(resultats.getInt(1) + " :  "+resultats.getString(2)+" "+
          resultats.getString(3)+"("+resultats.getDate(4)+")");
        System.out.println();
        encore = resultats.next();
      }

      resultats.close();
    } catch (SQLException e) {
      arret(e.getMessage());
    }

    affiche("fin du programme");
    System.exit(0);
  }
}

Pour exécuter la requête, l'interface PreparedStatement propose deux méthodes :

 

22.9. L'utilisation des transactions

Une transaction permet de ne valider un ensemble de traitements sur le base de données que si ils se sont tous effectués correctement.

Par exemple, une opération bancaire de transfert de fond d'un compte vers un autre oblige à la réalisation de l'opération de débit sur un compte et de l'opération de crédit sur l'autre compte. La réalisation d'une seule de ces opérations laisserait les données de la base dans un état inconsistant.

Une transaction est un mécanisme qui permet donc de s'assurer que toutes les opérations qui la compose seront réellement effectuées.

Une transaction est gérée à partir de l'objet Connection. Par défaut, une connection est en mode auto-commit. Dans ce mode, chaque opération est validée unitairement pour former la transaction.

Pour pouvoir rassembler plusieurs traitements dans une transaction, il faut tout d'abord désactiver le mode auto-commit. La classe Connection possède la méthode setAutoCommit() qui attend un booléen qui précise le mode de fonctionnement.

Exemple :

connection.setAutoCommit(false);

Une fois le mode auto-commit désactivé, un appel à la méthode commit() de la classe Connection permet de valider la transaction courante. L'appel à cette méthode valide la transaction courante et créé implicitement une nouvelle transaction.

Si une anomalie intervient durant la transaction, il est possible de faire un retour en arrière pour revenir à la situation de la base de données au début de la transaction en appellant la méthode rollback() de la classe Connection.

 

22.10. Les procédures stockées

L'interface CallableStatement définit les méthodes pour un objet qui va permettre d'appeler une procédure stockée. 

Cette interface hérite de l'interface PreparedStatement.

Un  objet qui implémente l'interface CallableStatement est obtenu en utilisant la méthode prepareCall() d'un objet de type Connection. Cette méthode attend en paramètre une chaîne de caractères contenant la chaîne d'appel de la procédure stockée.

L'appel d'une procédure étant particulier à chaque base de données supportant une telle fonctionnalité, JDBC propose une syntaxe unifiée qui sera transcrite par le pilote en un appel natif à la base de données. Cette syntaxe peut prendre plusieurs formes :

Un ensemble de méthode setXXX() (ou XXX représente un type primitif ou certains objets tel que String, Date, Object, ...) permet de fournir les valeurs de chaque paramètre défini dans la requête. Le premier paramètre de ces méthodes précise le numéro du paramètre dont la méthode va fournir la valeur. Le second paramètre précise cette valeur.

Un ensemble de méthode getXXX() (ou XXX représente un type primitif ou certains objets tel que String, Date, Object, ...) permet d'obtenir la valeur du paramètre de retour en fournissant la valeur 0 comme index de départ et un autre index pour les paramètres définis en entrée/sortie dans la procédure stockée.

Pour exécuter la requête, l'interface PreparedStatement propose deux méthodes :

 

22.11. Le traitement des erreurs JDBC

JDBC permet de connaitre les avertissements et les exceptions générées par la base de données lors de l'exécution de requête.

La classe SQLException représente les erreurs émises par la base de données. Elle contient trois attributs qui permettent de préciser l'erreur :

La classe SQLException possède une méthode getNextException() qui permet d'obtenir les autres exceptions levées durant la requête. La méthode renvoie null une fois la dernière exception renvoyée.

Exemple ( code Java 1.1 ) :
package com.jmd.test.dej;

import java.sql.*;

public class TestJDBC3 {

  private static void affiche(String message) {
    System.out.println(message);
  }

  private static void arret(String message) {
    System.err.println(message);
    System.exit(99);
  }

  public static void main(java.lang.String[] args) {
    Connection con = null;
    ResultSet resultats = null;
    String requete = "";

    try {
      Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
    } catch (ClassNotFoundException e) {
      arret("Impossible de charger le pilote jdbc:odbc");
    }

    affiche("connection a la base de données");
    try {

      String DBurl = "jdbc:odbc:testDB";
      con = DriverManager.getConnection(DBurl);

      requete = "SELECT * FROM tableinexistante";

      Statement stmt = con.createStatement();
      resultats = stmt.executeQuery(requete);

      affiche("parcours des données retournées");

      boolean encore = resultats.next();

      while (encore) {
        System.out.print(resultats.getInt(1) + " :  " + resultats.getString(2) + 
          " " + resultats.getString(3) + "(" + resultats.getDate(4) + ")");
        System.out.println();
        encore = resultats.next();
      }

      resultats.close();
    } catch (SQLException e) {
      System.out.println("SQLException");
      do {
        System.out.println("SQLState : " + e.getSQLState());
        System.out.println("Description :  " + e.getMessage());
        System.out.println("code erreur :   " + e.getErrorCode());
        System.out.println("");
        e = e.getNextException();
      } while (e != null);
      arret("");
    } catch (Exception e) {
      e.printStackTrace();
    }

    affiche("fin du programme");
    System.exit(0);
  }
}

 

22.12. JDBC 2.0

La version 2.0 de l'API JDBC a été intégrée au JDK 1.2. Cette nouvelle version apporte plusieurs fonctionnalités très intéréssantes dont les principales sont :

L'API JDBC 2.0 est séparée en deux parties :

 

22.12.1. Les fonctionnalités de l'objet ResultSet

Les possibilités de l'objet ResultSet dans la version 1.0 de JDBC sont très limitées : parcours séquentiel de chaque occurence de la table retournée.

La version 2.0 apporte de nombreuses améliorations à cet objet : le parcours des occurences dans les deux sens et la possibilité de faire des mises à jour sur une occurence.

Concernant le parcours, il est possible de préciser trois modes de fonctionnement :

Il est aussi possible de préciser si le ResultSet peut être mise à jour ou non :

C'est à la création d'un objet de type Statement qu'il faut préciser ces deux modes. Si ces deux modes ne sont pas précisés, ce sont les caractéristiques de la version 1.0 de JDBC qui sont utilisées (TYPE_FORWARD_ONLY et CONCUR_READ_ONLY).

Exemple ( code jdbc 2.0 ) :
  Statement statement = connection.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE,
    ResultSet.CONCUR_READ_ONLY);
  ResultSet resultSet = statement.executeQuery("SELECT nom, prenom FROM employes");

 

Le support de ces fonctionnalités est optionnel pour un pilote. L'objet DatabaseMetadata possède la méthode supportsResultSetType() qui attend en paramètre une constante qui représente une caractéristique : la méthode renvoie un booléen qui indique si la caractéristique est supportée ou non.

A la création du ResultSet, le curseur est positionné avant la première occurence à traiter. Pour se déplacer dans l'ensemble des occurences, il y a toujours la méthode next() pour se déplacer sur le suivant mais aussi plusieurs autres méthodes pour permettre le parcours des occurences en fonctions du mode utilisé dont les principales sont :

Méthode Rôle
boolean isBeforeFirst() renvoie un booleen qui indique si la position courante du curseur se trouve avant la première ligne
boolean isAfterLast() renvoie un booleen qui indique si la position courante du curseur se trouve après la dernière ligne
boolean isFirst() renvoie un booleen qui indique si le curseur est positionné sur la première ligne
boolean isLast() renvoie un booleen qui indique si le curseur est positionné sur la dernière ligne
boolean first() déplace le curseur sur la première ligne
boolean last() déplace le curseur sur la dernière ligne
boolean absolute() déplace le curseur sur la ligne dont le numéro est fournie en paramètre à partir du début si il est positif et à partir de la fin si il est négatif. 1 déplace sur la première ligne, -1 sur la dernière, -2 sur l'avant dernière ...
boolean relative(int) déplace le curseur du nombre de lignes fourni en paramètre par rapport à la position courante du curseur. Le paramètre doit être négatif pour se déplacer vers le début et positif pur se déplacer vers la fin. Avant l'appel de cette méthode, il faut obligatoirement que le curseur soit positionné sur une ligne.
boolean previous() déplace le curseur sur la ligne précédente. Le booleen indique si la première occurence est dépassée.
void afterLast() déplace le curseur après la dernière ligne
void beforeFirst() déplace le curseur avant la première ligne
int getRow() renvoie le numéro de la ligne courante

 

Exemple ( code jdbc 2.0 ) :
  Statement statement = connection.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE,
    ResultSet.CONCUR_READ_ONLY);
  ResultSet resultSet = statement.executeQuery(
    "SELECT nom, prenom FROM employes ORDER BY nom");
  resultSet.afterLast();
  while (resultSet.previous()) {
    System.out.println(resultSet.getString("nom")+
      " "+resultSet.getString("prenom"));
  }

Durant le parcours d'un ResultSet, il est possible d'effectuer des mises à jour sur la ligne courante du curseur. Pour cela, il faut déclarer l'objet ResultSet comme acceptant les mises à jour. Avec les versions précédentes de JDBC, il fallait utiliser la méthode executeUpdate() avec une requête SQL.

Maintenant pour réaliser ces mises à jour, JDBC 2.0 propose de les réaliser via des appels de méthodes plutôt que d'utiliser des requêtes SQL.

Méthode Rôle
updateXXX(String, XXX) permet de mettre à jour la colonne dont le nom est fourni en paramètre. Le type Java de cette colonne est XXX
updateXXX(int, XXX) permet de mettre à jour la colonne dont l'index est fourni en paramètre. Le type Java de cette colonne est XXX
updateRow() permet d'actualiser les modifications réalisées avec des appels à updateXXX()
boolean rowsUpdated() indique si la ligne courante a été modifiée
deleteRow() supprime la ligne courante
rowDeleted() indique si la ligne courante est supprimée
moveToInsertRow() permet de créer une nouvelle ligne dans l'ensemble de résultat
inserRow() permet de valider la création de la ligne

Pour réaliser une mise à jour dans la ligne courante désignée par le curseur, il faut utiliser une des méthodes updateXXX() sur chacun des champs à modifier. Une fois toutes les modifications faites dans une ligne, il faut appeler la méthode updateRow() pour reporter ces modifications dans la base de données car les méthodes updateXXX() ne font des mises à jour que dans le jeu de résultats. Les mises à jour sont perdues si un changement de ligne intervient avant l'appel à la méthode updateRow().

La méthode cancelRowUpdates() permet d'annuler toutes les modifications faites dans la ligne. L'appel à cette méthode doit être effectué avant l'appel à la méthode updateRow().

Pour insérer une nouvelle ligne dans le jeu de résultat, il faut tout d'abord appeler la méthode moveToInsertRow(). Cette méthode déplace le curseur vers un buffer dédié à la création d'une nouvelle ligne. Il faut alimenter chacun des champs nécessaires dans cette nouvelle ligne. Pour valider la création de cette nouvelle ligne, il faut appeler la méthode insertRow().

Pour supprimer la ligne courante, il faut appeler la méthode deleteRow(). Cette méthode agit sur le jeu de résultats et sur la base de données.

 

22.12.2. Les mises à jour de masse (Batch Updates)

JDBC 2.0 permet de réaliser des mises à jour de masse en regroupant plusieurs traitements pour les envoyer en une seule fois au SGBD. Ceci permet d'améliorer les performances surtout si le nombre de traitements est important.

Cette fonctionnalité n'est pas obligatoirement supportée par le pilote. La méthode supportsBatchUpdate() de la classe DatabaseMetaData permet de savoir si elle est utilisable avec le pilote.

Plusieurs méthodes ont été ajoutées à l'interface Statement pour pouvoir utiliser les mises à jour de masse :

Méthode Rôle
void addBatch(String) permet d'ajouter une chaîne contenant une requete SQL
int[] executeBatch() permet d'exécuter toutes les requêtes. Ell renvoit un tableau d'entier qui contient pour chaque requête, le nombre de mises à jour affectuées.
void clearBatch() supprime toutes les requêtes stockées

Lors de l'utilisation de batchupdate, il est préférable de positionner l'attribut autocommit à false afin de faciliter la gestion des transactions et le traitement d'une erreur dans l'exécution d'un ou plusieurs traitements.

Exemple ( code jdbc 2.0 ) :
  connection.setAutoCommit(false);
  Statement statement = connection.createStatement();

  for(int i=0; i<10 ; i++) {
     statement.addBatch("INSERT INTO personne VALUES('nom"+i+"','prenom"+i+"')");
  }
  statement.executeBatch();

Une exception particulière peut être levée en plus de l'exception SQLException lors de l'exécution d'une mise à jour de masse. L'exception SQLException est levée si une requête SQL d'intérrogation doit être exécutée (requête de type SELECT). L'exception BatchUpdateException est levée si une des requêtes de mise à jour échoue.

L'exception BatchUpdateException possède une méthode getUpdateCounts() qui renvoie un tableau d'entier qui contient le nombre d'occurences impactées par chaque requête réussie.

 

22.12.3. Le package javax.sql

Ce package est une extension à l'API JDBC qui propose des fonctionnalités pour les développements coté serveur. C'est pour cette raison, que cette extension est uniquement intégrée à J2EE.

Les principales fonctionnalités proposées sont :

DataSource et Rowset peuvent être utilisées directement. Les pools de connections et les transactions distribuées sont utilisés par une implémentation dans les serveurs d'applications pour fournir ces services.

 

22.12.4. La classe DataSource

La classe DataSource propose de fournir une meilleure alternative à la classe DriverManager pour assurer la connection à une base de données.

Elle représente une connection physique à une base de données. Les fournisseurs de pilotes proposent une implémentation de l'interface DataSource.

L'utilisation d'un objet DataSource est obligatoire pour pouvoir utiliser un pool de connection et les transactions distribuées. Une fois créé un objet de type DataSource doit être enregistré dans un service de nommage. Il suffit alors d'utiliser JNDI pour obtenir une instance de classe DataSource.

 

Exemple
...
Context ctx = new InitialContext();
DataSource ds = (DataSource) ctx.lookup("jdbc/applicationDB");
Connection con = ds.getConnection("admin", "mpadmin");
...

 

en construction
La suite de cette section sera développée dans une version future de ce document

 

22.12.5. Les pools de connexion

Un pool de connexions permet de maintenir un ensemble de connections établies vers une base de données qui sont réutilisables. L'établissement d'une connexion est très couteux en ressources. L'intérêt du pool de connexions est de limiter le nombre de ces créations et ainsi d'améliorer les performances surtout si le nombre de connexions est important.

 

en construction
La suite de cette section sera développée dans une version future de ce document

 

22.12.6. Les transactions distribuées

Les connexions obtenues à partir d'un objet DataSource peuvent participer à une transaction distribuée.

 

en construction
La suite de cette section sera développée dans une version future de ce document

 

22.12.7. L'API RowSet

 

en construction
Cette section sera développée dans une version future de ce document

 

22.13. JDBC 3.0

La version 3.0 de l'API JDBC a été intégrée au J2SE 1.4.

 

en construction
La suite de cette section sera développée dans une version future de ce document

 

22.14. MySQL et Java

MySQL est une des bases de données open source les plus populaires.

 

22.14.1. Installation sous Windows

Il suffit de télécharger le fichier mysql-3.23.49-win.zip sur le site www.mysql.com, de décompresser ce fichier dans un repertoire et d'exécuter le fichier setup.exe

Il faut ensuite télécharger le pilote ODBC, MyODBC-3.51.03.exe, et l'exécuter

 

22.14.2. Opérations de base avec MySQL

Cette section est une présentation rapide de quelques fonctionnalités de base pour pouvoir utiliser MySQL. Pour un complément d'informations sur toutes les possibilités de MySQL, consultez la documentation de cet excellent outil.

Pour utiliser MySQL, il faut s'assurer que le serveur est lancé sinon il faut exécuter la command c:\mysql\bin\mysqld-max

Pour exécuter des commandes SQL, il faut utiliser l'outil c:\mysql\bin\mysql. Cet outil est un interpreteur de commandes en mode console.

Exemple : pour voir les databases existantes
mysql>show databases;
+----------+
| Database |
+----------+
| mysql    |
| test     |
+----------+
2 rows in set (0.00 sec)

Un des premières choses à faire, c'est de créer une base de données qui va recevoir les différentes tables.

Exemple : Pour créer une nouvelle base de données nommée ‘testjava'
mysql> create database testjava;
Query OK, 1 row affected (0.00 sec)

mysql>use testjava;
Database changed

Cette nouvelle base de données ne contient aucune table. Il faut créer la ou les tables utiles aux développements.

Exemple : Création d'une table nommée personne contenant trois champs : nom, prenom et date de naissance
mysql> show tables;
Empty set (0.06 sec)

mysql> create table personne (nom varchar(30), prenom varchar(30), datenais date
);
Query OK, 0 rows affected (0.00 sec)
 
mysql>show tables;
+--------------------+
| Tables_in_testjava |
+--------------------+
| personne           |
+--------------------+
1 row in set (0.00 sec)

Pour voir la définition d'une table, il faut utiliser la commande DESCRIBE :

Exemple : voir la définition de la table
mysql> describe personne;
+----------+-------------+------+-----+---------+-------+
| Field    | Type        | Null | Key | Default | Extra |
+----------+-------------+------+-----+---------+-------+
| nom      | varchar(30) | YES  |     | NULL    |       |
| prenom   | varchar(30) | YES  |     | NULL    |       |
| datenais | date        | YES  |     | NULL    |       |
+----------+-------------+------+-----+---------+-------+
3 rows in set (0.00 sec)

Cette table ne contient aucun enregistrement. Pour ajouter un enregistrement, il faut utiliser la command SQL insert.

Exemple : insertion d'une ligne dans la table
mysql> select * from personne;
Empty set (0.00 sec)

mysql> insert into personne values ('Nom 1','Prenom 1','1970-08-11');
Query OK, 1 row affected (0.05 sec)

mysql> select * from personne;
+-------+----------+------------+
| nom   | prenom   | datenais   |
+-------+----------+------------+
| Nom 1 | Prenom 1 | 1970-08-11 |
+-------+----------+------------+
1 row in set (0.00 sec)

Il existe des outils graphiques libres ou commerciaux pour faciliter l'administration et l'utilisation de MySQL.

 

22.14.3. Utilisation de MySQL avec Java via ODBC

Sous Windows, il est possible d'utiliser une base de données MySQL avec Java en utilisant ODBC. Dans ce cas, il faut définir une source de données ODBC sur la base de données et l'utiliser avec le pilote de type 1 fourni en standard avec J2SE.

 

22.14.3.1. Déclaration d'une source de données ODBC vers la base de données

Dans le panneau de configuration, cliquez sur l'icône « Source de données ODBC ».

Le plus simple est de créer une source de données Systeme qui pourra être utilisée par tous les utilisateurs en cliquant sur l'onglet " DSN système "

Pour ajouter une nouvelle source de données, il suffit de cliquer sur le bouton "Ajouter ... ". Une boîte de dialogue permet de sélectionner le type de pilote qui sera utilisé par la source de données.

Il faut sélectionner le pilote MySQL et cliquer sur le bouton "Finish".

Une nouvelle boîte de dialogue permet de renseigner les informations sur la base de données à utiliser notamment le nom de DSN et le nom de la base de données.

Pour verifier si la connection est possible, il suffit de cliquer sur le bouton « Test Data Source »

Cliquer sur Ok pour fermer la fenêtre et cliquer sur Ok pour valider les paramètres et créer la source de données.

La source de données est créée.

22.14.3.2. Utilisation de la source de données

Pour utiliser la source de données, il faut écrire et tester une classe Java. La seule particularité est l'utilisation du pont JDBC-ODBC comme pilote JDBC et l'URL spécifique à ce pilote qui contient le nom de la source de données définie.

Exemple
import java.sql.*;

public class TestJDBC10 {

  private static void affiche(String message) {
    System.out.println(message);
  }

  private static void arret(String message) {
    System.err.println(message);
    System.exit(99);
  }

  public static void main(java.lang.String[] args) {
    Connection con = null;
    ResultSet resultats = null;
    String requete = "";

     // chargement du pilote
    try {
      Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
    } catch (ClassNotFoundException e) {
      arret("Impossible de charger le pilote jdbc:odbc");
    }

    //connection a la base de données
    affiche("connection a la base de donnees");
    try {

      String DBurl = "jdbc:odbc:test_java";
      con = DriverManager.getConnection(DBurl);
    } catch (SQLException e) {
      arret("Connection à la base de donnees impossible");
    }
     
    //creation et execution de la requête
    affiche("creation et execution de la requête");
    requete = "SELECT * FROM personne";

    try {
      Statement stmt = con.createStatement();
      resultats = stmt.executeQuery(requete);
    } catch (SQLException e) {
      arret("Anomalie lors de l'execution de la requête");
    }
     
    //parcours des données retournees
    affiche("parcours des données retournees");
    try {
      ResultSetMetaData rsmd = resultats.getMetaData();
      int nbCols = rsmd.getColumnCount();
      boolean encore = resultats.next();

      while (encore) {

        for (int i = 1; i <= nbCols; i++)
          System.out.print(resultats.getString(i) + "");
     
        System.out.println();
     
        encore = resultats.next();
      }

      resultats.close();
    } catch (SQLException e) {
      arret(e.getMessage());
    }
   
    affiche("fin du programme");
    System.exit(0);
  }
}

Resultat :
C:\$user>javac TestJDBC10.java
C:\$user>java TestJDBC10
connection a la base de donnees
creation et execution de la requ_te
parcours des donn_es retournees
Nom 1 Prenom 1 1970-08-11
fin du programme

22.14.4. Utilisation de MySQL avec Java via un pilote JDBC

mm.mysql est un pilote JDBC de type IV développé sous licence LGPL par Mark Matthews pour accéder à une base de données MySQL.

Le téléchargement du pilote JDBC se fait sur le site http://mmmysql.sourceforge.net/ . Le fichier mm.mysql-2.0.14-you-must-unjar-me.jar contient les sources et les binaires du pilote.

Pour utiliser cette archive, il faut la décompresser, par exemple dans le répertoire d'installation de mysql.

Il faut s'assurer que les fichiers jar sont accessibles dans le classpath ou les préciser manuellement lors de la compilation et de l'exécution comme dans l'exemple ci dessous.

Exemple
import java.sql.*;

public class TestJDBC11 {

  private static void affiche(String message) {
    System.out.println(message);
  }

  private static void arret(String message) {
    System.err.println(message);
    System.exit(99);
  }

  public static void main(java.lang.String[] args) {
    Connection con = null;
    ResultSetresultats = null;
    String requete = "";
  
    // chargement du pilote
    try {
      Class.forName("org.gjt.mm.mysql.Driver").newInstance();
    } catch (Exception e) {
      arret("Impossible decharger le pilote jdbc pour mySQL");
    }

    //connection a la base de données
    affiche("connection a la base de donnees");
    try {

      String DBurl = "jdbc:mysql://localhost/testjava";
      con = DriverManager.getConnection(DBurl);
    } catch (SQLException e) {
      arret("Connection a la base de donnees impossible");
    }

    //creation et execution de la requête
    affiche("creation et execution dela requête");
    requete = "SELECT * FROM personne";

    try {
      Statement stmt = con.createStatement();
      resultats = stmt.executeQuery(requete);
    } catch (SQLException e) {
      arret("Anomalie lors de l'execution de la requete");
    }
 
    //parcours des données retournees
    affiche("Parcours des donnees retournees");
    try {
      ResultSetMetaData rsmd = resultats.getMetaData();
      int nbCols = rsmd.getColumnCount();
      boolean encore = resultats.next();

      while (encore) {

        for (int i = 1; i <= nbCols; i++)
          System.out.print(resultats.getString(i) + "");

        System.out.println();
        encore = resultats.next();
      }

      resultats.close();
    } catch (SQLException e) {
      arret(e.getMessage());
    }

    affiche("fin du programme");
    System.exit(0);
  }
}

Le programme est identique au précédent utilisant ODBC sauf :

Resultat :
C:\$user>javac -classpath c:\j2sdk1.4.0-rc\jre\lib\mm.mysql-2.0.14-bin.jar TestJDBC11.java
C:\$user>
C:\$user>java -cp .;c:\j2sdk1.4.0-rc\jre\lib\mm.mysql-2.0.14-bin.jar TestJDBC11
connection a la base de donnees
creation et execution de la requ_te
Parcours des donnees retournees
Nom 1 Prenom 1 1970-08-11
fin du programme


  21. L'interaction avec le réseau 23. La gestion dynamique des objets et l'introspection Imprimer Sommaire Consulter avec table des matières Développons en Java   v 0.85 béta  
Copyright (C) 1999-2005 Jean-Michel DOUDOUX