Pour accéder à une base de données de façon programmatique, on utilise un connecteur de base de données (lié au SGBD choisi). Ce connecteur est nommé JDBC (Java DataBase Connector).
Nous verrons une version de base, un peu plus simple mais non recommandée puis des améliorations rendant la connexion et manipulation plus propre. Enfin, nous utiliserons une API pour produire les résultats de requête sous forme structurée : en JSON.
Version de base
Dans netbeans, créons un nouveau projet Java with Ant > Application Java.
Une fois le projet créé, il faut ajouter “PostgreSQL JDBC Driver” dans les bibliothèques (Libraries) du projet netbeans.
Dans la classe principale, il faut définir l’URL de la base de données à laquelle on se connecte :
- protocole :
jdbc:postgresql - ip ou fqdn de l’hôte, par exemple
192.168.56.101oupostgresql-std-a.apps.kappsul.su.univ-lorraine.fr - port, par exemple
5432 - nom de la base, par exemple
imocadb
Ce qui donne l’URL <jdbc:postgresql://192.168.56.101:5432/imocadb> ou <jdbc:postgresql://postgresql-std-a.apps.kappsul.su.univ-lorraine.fr:5432/imocadb>.
- protocole :
On utilise un objet de la classe java.sql.Connection et la méthode statique DriverManager.getConnection pour se connecter à la base :
String url = "jdbc:postgresql://postgresql-std-a.apps.kappsul.su.univ-lorraine.fr:5432/imocadb"; String role = "postgres"; String mdp = "P4SSW0RD"; Connection cnx = DriverManager.getConnection(url, role, mdp);Remarque : cette dernière ligne peut échouer et donc lancer une exception
SQLExceptionqu’il conviendra de rattraper. On pourra utiliser la proposition de Netbeans de “Surround Block with try-catch”.On crée une requête (un PreparedStatement) avec la méthode
prepareStatementde la classeConnectionen mettant des ‘?’ à la place des valeurs qui seront remplies par l’utilisateur, puis on remplace ces ‘?’ par les valeurs souhaitées avecsetInt(pour des entiers) et enfin on exécute cette requête :int id = 12; PreparedStatement req = cnx.prepareStatement("SELECT rang, nombateau FROM classement WHERE idc = ?"); req.setInt(1, id); // remplace le premier ? par la valeur de la variable id. ResultSet rs = req.executeQuery();On parcourt le résultat de la requête :
while (rs.next()) { String ligne = rs.getInt("rang") + " " + rs.getString("nombateau"); System.out.println(ligne); }rs.next()permet d’aller à la ligne suivante de la table représentée par leResultSet rs. Cette méthode renvoie false quand il ne reste plus de ligne, ce qui nous fait sortir de la boucle. Pour accéder aux valeurs contenues dans la liste, on utilise les méthodes get dersen leur passant les noms de colonnes. Ainsi,rs.getInt("rang")permet d’obtenir l’entier (int) contenue dans la case de la colonnerang.On ferme la connexion à la base de données :
cnx.close();Une bonne pratique consiste à écrire cette ligne aussitôt après celle où on crée la connexion avec le DriverManager.
Le code ci-dessus nécessite les import suivants :
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;Utilisation de try with resources
Le code précédent devrait comporter un gros bloc inclus dans un try {...}.
Quand une ligne de ce bloc échoue, l’exécution de ce bloc est interrompue et on “rattrape” l’erreur avec le bloc catch.
Mais la ligne cnx.close() devrait être exécutée qu’il y ait eu exception ou non.
Si on oublie cette ligne ou qu’elle ne s’exécute pas systématiquement, des connexions ouvertes vont s’accumuler et cela finira par poser problème.
Une façon élégante de résoudre ce problème est d’utiliser un “try with resource” qui va fermer la ressource en cas d’erreur comme en cas de succès sans qu’il soit besoin de l’écrire.
La syntaxe consiste simplement à mettre la ligne qui crée la ressource (ici la Connection) entre parenthèses entre le mot-clef try et son accolade ouvrante :
try (Connection cnx = DriverManager.getConnection(url, login, passwd)) {
PreparedStatement stmt = cnx.prepareStatement(...
...
} catch (SQLException ex) {
...
}Version Collection
Au lieu d’afficher les valeurs trouvées nous allons vouloir renvoyer un fichier json contenant les données. Pour cela, nous utilisons une Collection qui sera implémentée à l’aide d’une ArrayList.
Créer une classe qui va encapsuler les données obtenues dans la requête, ici le rang et le nombateau :
public class LigneClassement { int rang; String nombateau; public LigneClassement(int rang, String nombateau) { this.rang = rang; this.nombateau = nombateau; } }Pour le parcours du résultat de requête, nous allons créer une collection de
LigneClassements et la remplir :Collection<LigneClassement> cl = new ArrayList<>(); while (rs.next()) { LigneClassement ligne = new LigneClassement(rs.getInt("rang") rs.getString("nombateau")); cl.add(ligne); }
Note : la manipulation de collections nécessite dimporter les classes Collection et ArrayList.
import java.util.ArrayList;
import java.util.Collection;Export en JSON
Maintenant que les données utiles sont encapsulées dans une collection, il est possible de les sérialiser en JSON. Pour cela, nous allons utiliser la bibliothèque de manipulation de json com.google.gson.
Ajouter dans les Bibliothèques/Libraries du projet le fichier
gson.jar.Nous allons créer un objet Gson à partir de la collection
cl:Gson gson = new GsonBuilder().setPrettyPrinting().create(); String res = gson.toJson(cl); System.out.println(res);
Remarque : il faut ajouter les imports suivants :
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
Livrables
Créer des classes Bateau, Skipper et Course qui exécutent les requêtesSQL pour (respectivement) :
- obtenir un json du palmarès (rang, nombateau, nomskipper, prenomskipper, edition) d’un bateau désigné par son idb.
- obtenir un json du palmarès (rang, nombateau, edition) d’un skipper désigné par son ids.
- obtenir un json du classement (rang, nombateau, nomskipper, prenomskipper) d’une édition désignée par son idc.
Créer les classes utilitaires qui permettent d’encapsuler les données dans des collections.