Cours BUT Info

Ch. 3 — JDBC : du programme à la base

Connecter un programme Java à une base : le pilote et son classpath, l'URL de connexion propre à chaque SGBD, Statement et ResultSet, la fermeture propre en try-with-resources — et la lecture des messages d'erreur.

Après les rappels SQL et le chapitre client-serveur/ODBC, on passe au code : comment un programme Java dialogue avec une base de données. La réponse s'appelle JDBC — Java DataBase Connectivity — et c'est l'équivalent Java d'ODBC : une API standard, fournie avec Java SE, qui permet au même programme de parler à n'importe quel SGBD, à condition de disposer de son pilote.

1. JDBC, c'est quoi ?

  • une API Java pour la connexion aux bases de données, fournie avec Java SE (paquetage java.sql) ;
  • indépendante du système (contrairement à ODBC) et indépendante de la base de données (comme ODBC) ;
  • le paquetage java.sql contient :
CatégorieÀ retenir
InterfacesConnection, Statement, PreparedStatement, ResultSet, Driver…
ClassesDriverManager, Date, Time, Timestamp, Types…
ExceptionsSQLException (et BatchUpdateException, SQLWarning)

Ces noms sont le vocabulaire de tout le chapitre — on les croise dans chaque programme.

La chaîne JDBC, cousine d'ODBC : le pilote — une archive .jar fournie par le SGBD, mais installée sur le POSTE CLIENT — traduit les appels de l'API java.sql en langage natif de la base. Il n'est pas nécessaire à la compilation (class loader) : seulement à l'exécution.

2. Enregistrer le pilote

Comme pour ODBC, chaque base de données utilise un pilote (driver) qui lui est propre : c'est lui qui convertit les appels JDBC dans le langage natif du SGBD. La première étape d'un programme, c'est de l'enregistrer :

Class.forName("org.gjt.mm.mysql.Driver");     // MySQL
Class.forName("oracle.jdbc.driver.OracleDriver"); // Oracle
Class.forName("org.postgresql.Driver");       // PostgreSQL
Class.forName("org.h2.Driver");               // H2

Où vit le pilote ?

C'est le serveur qui fournit le pilote, sous forme d'une archive jar — mais le pilote est toujours installé sur le poste client ! C'est votre programme qui doit pouvoir le charger au moment de l'exécution (voir section 8).

3. L'URL de connexion

Une fois le pilote enregistré, on établit la connexion avec la base :

Connection con = DriverManager.getConnection(URL, utilisateur, mdp);

Chaque pilote possède sa propre syntaxe d'adresse :

url = "jdbc:mysql://localhost:3306/mabase";
url = "jdbc:oracle:thin:@127.0.0.1:8888:mabase";
url = "jdbc:postgresql://localhost:5432/mabase";
url = "jdbc:h2:tcp://localhost:9092/~/mabase";

L'URL de la base dépend du pilote — on retrouve la machine, le port (5432 pour PostgreSQL) et le nom de la base. C'est le même découpage qu'en ODBC, avec une autre grammaire.

4. Un test de connexion

Le programme minimal du cours : enregistrer le pilote, se connecter, fermer, annoncer la couleur.

import java.sql.*;

public class TestConnect {
    public static void main(String args[]) throws Exception {
        // enregistrement du driver
        Class.forName("org.postgresql.Driver");
        // connexion à la base
        String url = "jdbc:postgresql://machine/base";
        String nom = "xxx";
        String mdp = "yyy";
        Connection con = DriverManager.getConnection(url, nom, mdp);
        // fermeture de la connexion
        con.close();
        System.out.println("All is ok !");
    }
}

À la compilation et à l'exécution, une différence capitale :

javac TestConnect.java                                    # le driver n'est pas nécessaire
java -cp ~/jars/mondriver.jar:. TestConnect               # mais il est impératif ici !

Grâce au class loader, le pilote n'est pas nécessaire à la compilation. Il est impératif à l'exécution, et il se déclare sur le classpath. Rappels sur -cp :

  • dès que -cp est utilisé, il faut spécifier tous les chemins de recherche des classes et .jar ;
  • quand les dépendances se multiplient, préférez la variable système CLASSPATH ;
  • attention : CLASSPATH ≠ PATH (l'un cherche des classes, l'autre des exécutables).

5. Interroger la base

La technique générale tient en trois temps :

  1. créer une zone de gestion de requête : Statement stmt = con.createStatement();
  2. exécuter la requête :
    • ResultSet rs = stmt.executeQuery(query); pour un SELECT ;
    • int nb = stmt.executeUpdate(query); pour INSERT, UPDATE, DELETE (renvoie le nombre de lignes touchées) ;
  3. fermer les espaces : close() des objets ResultSet, Statement et Connection.

Toujours fermer la connexion !

Bonne nouvelle : close() sur la connexion entraîne automatiquement la fermeture des autres objets. Mauvaise nouvelle : il faut penser à l'appeler, y compris quand le programme se passe mal (section 7).

Les trois exemples du cours, en entier :

Créer une table
import java.sql.*;

public class Create {
    public static void main(String args[]) throws Exception {
        Class.forName("org.postgresql.Driver");
        String url = "jdbc:postgresql://localhost/mabase";
        Connection con = DriverManager.getConnection(url, "mathieu", "xxx");
        Statement stmt = con.createStatement();
        stmt.executeUpdate("create table CLIENT " +
                "(CNO int PRIMARY KEY, NOM varchar(10), PRENOM varchar(10), AGE int)");
        con.close();
        System.out.println("All is ok !");
    }
}
Insérer un tuple
// …
Statement stmt = con.createStatement();
stmt.executeUpdate("insert into CLIENT " +
        "values(1,'Durand','paul',10)");
con.close();
Sélectionner et parcourir
// …
Statement stmt = con.createStatement();
String query = "select NOM,PRENOM,AGE from CLIENT";
ResultSet rs = stmt.executeQuery(query);
System.out.println("Liste des clients:");
while (rs.next()) {
    String n = rs.getString("nom");   // col 1
    String p = rs.getString("prenom"); // col 2
    int a = rs.getInt("age");          // col 3
    System.out.println(n + " " + p + " " + a);
}
con.close();

Lire un ResultSet

executeQuery renvoie un objet de type ResultSet avec deux méthodes à connaître :

  • next() — permet de parcourir le ResultSet : renvoie true tant qu'il y a des tuples. Impossible de revenir en arrière !
  • getXXX(arg) — permet de lire une donnée dans une variable de type XXX (getInt, getFloat, getDate, getLong, getString…). Le type est celui de la variable qui récupère le résultat. L'argument peut être le nom de la colonne ou son numéro : String pds = rs.getString("poids");
Un ResultSet se parcourt comme un curseur : next() descend d'un tuple (true tant qu'il en reste) et getXXX lit la ligne courante. Le parcours ne va que vers l'avant — il n'y a pas de previous().

6. Passer des paramètres… par concaténation

Très souvent, la requête a besoin de valeurs rangées dans des variables Java. Le lien entre Java et SQL se fait par concaténation de chaînes :

int age = 10;
String query = "select * from clients where age > " + age;

String nom = "Durand";
String query2 = "select * from clients where nom like '" + nom + "'";

Pensez à systématiquement afficher la requête fabriquée

Avant de l'exécuter, affichez la requête construite : c'est le moyen le plus rapide de voir une apostrophe mal placée ou une variable vide. Et gardez en tête que cette concaténation a des limites — le chapitre 4 présente PreparedStatement, la façon propre de passer des paramètres.

7. Fermer proprement : try/finally, puis try-with-resources

La fermeture des connexions est capitale — que le programme se passe bien ou mal. Première version, à la main :

Connection con = null;
try {
    Class.forName("org.postgresql.Driver");
    con = DriverManager.getConnection(url, nom, mdp);
    // … les requêtes …
} catch (Exception e) {
    // affichage ou traitement de l'erreur : impératif
} finally {
    try { con.close(); } catch (Exception e2) { }
}

Depuis Java 7, on peut faire beaucoup mieux : les objets Connection implémentent l'interface Closeable, donc try-with-resources :

try (Connection con = DriverManager.getConnection(url, nom, mdp)) {
    // … les requêtes …
} catch (Exception e) {
    // affichage ou traitement de l'erreur : impératif
}

Dans le cas d'un Closeable, la méthode close est appelée automatiquement dans tous les cas — succès, exception, retour anticipé. C'est la version à retenir.

8. Pourquoi le class loader et pas un import ?

On peut enregistrer le pilote par réflexion (Class.forName("org.postgresql.Driver")) ou l'importer classiquement (import org.postgresql.*) — mais le class loader a un avantage décisif :

  • il permet de changer d'implémentation sans recompiler ;
  • le programme devient indépendant du SGBD (il ne fait référence qu'à java.sql) ;
  • conséquence : javac n'a pas besoin du driver, c'est java qui en a besoin.

9. Gérer les erreurs

Les erreurs classiques du premier contact :

MessageCause probable
ClassNotFoundExceptionproblème de classpath (le .jar n'est pas trouvé)
java.net.ConnectException: Connection refusedla tentative de connexion a échoué (serveur éteint ? mauvais port ?)
role xxx does not existl'utilisateur est inconnu du SGBD

Comme toutes les entrées-sorties Java, les ordres JDBC déclenchent des exceptions qu'il faut impérativement traiter :

  • la classe Exception est affinée en SQLException ;
  • getMessage() permet d'obtenir le message en clair ;
  • printStackTrace() affiche la pile d'exécution.

Impératif : apprendre à lire les messages d'erreur !

10. Les 7 points capitaux

  1. le classpath contient tous les chemins de recherche des classes et .jar ;
  2. deux possibilités pour l'exprimer : l'option -cp, ou la variable CLASSPATH ($CLASSPATH sous Unix, %CLASSPATH% sous Windows) ;
  3. idéalement, on met la variable CLASSPATH dans le .bashrc pour tous les shells ;
  4. toujours afficher la requête avant son exécution ;
  5. toujours afficher e.getMessage() dans le catch ;
  6. sous Unix les séparateurs de répertoires sont /, sous Windows \ ;
  7. les variables système commencent par $ sous Unix et sont entourées de % sous Windows.

A retenir

  • JDBC est fourni avec Java SE (java.sql) : le même programme parle à n'importe quel SGBD, comme ODBC mais côté Java ;
  • le pilote (archive .jar) est installé sur le poste client ; il n'est pas nécessaire à la compilation, mais impératif à l'exécution (classpath !) ;
  • le cycle d'une requête : DriverManager.getConnection → createStatement → executeQuery / executeUpdate → ResultSet (next / getXXX) → close ;
  • on ferme toujours la connexion — en try-with-resources ;
  • devant une erreur : afficher la requête et e.getMessage(), et apprendre à les lire.

cours3-jdbc.pdf

transp_cours303 — Java DataBase Connectivity · 22 diapositives

Consultation seule — téléchargement désactivé

Chargement du document…

Dernière mise à jour le

Sur cette page