I. Introduction▲
Dans ce document, nous allons voir comment charger des données depuis une base MySQL à l'aide de JDBC, puis transformer ces données en objets de notre modèle.
La FAQ JDBC dit : « JDBC est une API (Application Programming Interface) Java disponible depuis la version 1.1 du JDK. Pour note, JDBC est un nom déposé et non un acronyme, même si en général on lui donne la définition suivante : Java DataBase Connectivity. »
I-A. À propos▲
Découvrir une technologie n'est pas chose facile. En aborder plusieurs d'un coup l'est encore moins. Partant de ce constat, cet article a été écrit pour aller à l'essentiel. Les points importants sont présentés dans le corps de l'article et les éléments secondaires sont expliqués en annexes.
I-B. Avant de commencer▲
Pour écrire ce tutoriel, j'ai utilisé les éléments suivants :
- Java JDK 1.6.0_24-b07 ;
- Eclipse Indigo 3.7 JEE 64b ;
- Maven 3.0.3 ;
- MySQL 5.1.52 (avec EasyPHP 5.3.3.1).
Ce document reprend une partie des éléments déjà programmés dans le cadre du tutoriel « Charger des données depuis un fichier CSV simple en 5 minutes » dont je vous recommande une lecture rapide.
II. Découverte du projet d'exemple▲
II-A. Télécharger, installer et importer le projet d'exemple▲
Pour commencer, je vous propose de télécharger le fichier Zip « notes4_domain-et-dao-csv.zip », contenant un projet Java-Maven d'exemple.
Compilez le projet d'exemple et importez-le dans Eclipse (comme expliqué dans le tutoriel « Importer un projet Maven dans Eclipse en 5 minutes ») ou dans l'IDE de votre choix.
Pour suivre ce tutoriel, vous pouvez vous contenter de lire les codes proposés ci-dessous (codes complets en annexes).
II-B. Ce que fait déjà le projet▲
Le projet contient un DAO (Data Access Object) qui va lire une liste d'élèves à partir d'un fichier CSV. Ce DAO peut se résumer par les codes suivants.
package com.thi.notes.dao;
import java.util.List;
import com.thi.notes.domain.Eleve;
public interface EleveDao {
List<Eleve> findEleves();
}package com.thi.notes.dao;
...
import com.thi.notes.domain.Eleve;
import com.thi.notes.domain.Sexe;
public class CsvEleveDao implements EleveDao {
@Override
public List<Eleve> findEleves() {
// Lire les donnees dans le fichier CSV
...
}
}Le DAO utilise donc les objets « Eleve » et « Sexe » dont les codes résumés sont les suivants.
package com.thi.notes.domain;
import java.util.Date;
public class Eleve {
private String nom;
private String prenom;
private Integer annee;
private Sexe sexe;
private Date dateNaissance;
private String adresse;
public Eleve(String nom, String prenom, Integer annee, Sexe sexe, Date dateNaissance, String adresse) {
...
}
// + getters et setters
}package com.thi.notes.domain;
public enum Sexe {
HOMME("Garçon"),
FEMME("Fille");
private final String label;
Sexe(String label) {
this.label = label;
}
public String getLabel() {
return label;
}
}Enfin, la classe « DaoLauncher » permet de tester la lecture.
package com.thi.notes;
import java.util.List;
import com.thi.notes.dao.CsvEleveDao;
import com.thi.notes.dao.EleveDao;
import com.thi.notes.domain.Eleve;
public class DaoLauncher {
public static void main(String[] args) {
EleveDao eleveDao = new CsvEleveDao();
List<Eleve> eleves = eleveDao.findEleves();
System.out.println("Liste des eleves");
for (Eleve eleve : eleves) {
System.out.println(eleve);
}
}
}II-C. Avant de commencer▲
Comme nous allons lire des données à partir de MySQL, encore faut-il avoir des tables et des valeurs dedans.
Si vous n'avez pas encore installé MySQL sur votre ordinateur, alors je vous conseille d'installer EasyPHP qui simplifie le processus d'installation et de configuration. En outre EasyPHP installe automatiquement le logiciel phpMyAdmin qui sera bien pratique pour manipuler la base.
Commencez par créer la base « notes_eleves » à l'aide du script SQL suivant.
CREATE DATABASE IF NOT EXISTS notes_eleves;Puis la structure de la table « eleve », qui va contenir (sans surprise) la liste des élèves.
CREATE TABLE IF NOT EXISTS `eleve` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`nom` varchar(100) DEFAULT NULL,
`prenom` varchar(100) DEFAULT NULL,
`classe` int(10) unsigned NOT NULL,
`date_naissance` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
`adresse` varchar(250) DEFAULT NULL,
`sexe` enum('HOMME','FEMME') NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=2 ;Puis insérez les données à l'aide du script suivant.
INSERT INTO `eleve` (`id`, `nom`, `prenom`, `classe`, `date_naissance`, `adresse`, `sexe`) VALUES
(1, 'Durand', 'Marie', 3, '1992-01-02 00:00:00', '15 rue du Lac 75001 Paris', 'FEMME'),
(2, 'Alesi', 'Julie', 3, '1992-01-08 00:00:00', '72 av. Jean Dupont 75003 Paris', 'FEMME'),
(3, 'Martini', 'Carine', 3, '1992-01-17 00:00:00', '2 rue du Moulin 92230 Neullavy', 'FEMME'),
(4, 'Varola', 'Sophie', 3, '1992-01-21 00:00:00', '15 rue du Lac 75001 Paris', 'FEMME'),
(5, 'Labiche', 'Lelou', 3, '1992-01-21 00:00:00', '15 rue du Lac 75001 Paris', 'FEMME'),
(6, 'Dujardin', 'Anne', 3, '1992-02-03 00:00:00', '67 rue des Jardins 91800 Brunoy', 'FEMME'),
(7, 'Laventure', 'Martine', 3, '1992-02-15 00:00:00', '15 rue du Lac 75001 Paris', 'FEMME'),
(8, 'Livradu', 'Alice', 3, '1992-02-18 00:00:00', '15 rue du Lac 75001 Paris', 'FEMME'),
(9, 'Veronicci', 'Cerise', 3, '1992-03-01 00:00:00', '15 rue du Lac 75001 Paris', 'FEMME'),
(10, 'Baladini', 'Mathilde', 3, '1992-03-12 00:00:00', '15 rue du Lac 75001 Paris', 'FEMME'),
(11, 'Michelet', 'Jean', 3, '1992-04-08 00:00:00', '15 rue du Lac 75001 Paris', 'HOMME'),
(12, 'Dupond', 'Pierre', 3, '1992-04-09 00:00:00', '15 rue du Lac 75001 Paris', 'HOMME'),
...
(48, 'Valegin', 'Jean', 3, '1992-11-28 00:00:00', '15 rue du Lac 75001 Paris', 'HOMME'),
(49, 'Eto', 'Gabin', 3, '1992-11-18 00:00:00', '15 rue du Lac 75001 Paris', 'HOMME'),
(50, 'Fivolini', 'Kevin', 3, '1992-12-06 00:00:00', '15 rue du Lac 75001 Paris', 'HOMME'),
(51, 'Laferme', 'Martin', 3, '1992-12-07 00:00:00', '15 rue du Lac 75001 Paris', 'HOMME'),
(52, 'Dupuis', 'Vincent', 3, '1992-12-15 00:00:00', '15 rue du Lac 75001 Paris', 'HOMME'),
(53, 'Lagrange', 'Alexandre', 3, '1992-12-28 00:00:00', '15 rue du Lac 75001 Paris', 'HOMME');Enfin, créez un nouvel utilisateur nommé « dvp » et ayant le mot de passe « hello » à l'aide du script SQL suivant.
CREATE USER 'dvp'@'%' IDENTIFIED BY 'hello';
GRANT SELECT , INSERT , UPDATE ON `notes\_eleves` . * TO 'dvp'@'%';III. Action▲
III-A. Compléter le modèle▲
Durée estimée : 30 secondes.
La classe Eleve ne possède pas encore l'attribut « id », ce à quoi nous allons immédiatement remédier en complétant ce « bean ».
public class Eleve {
private Integer id;
private String nom;
private String prenom;
private Integer annee;
private Sexe sexe;
private Date dateNaissance;
private String adresse;
...
// + getters et settersPour faire bonne mesure, nous allons ajouter un constructeur prenant ce nouvel attribut en paramètre.
public class Eleve {
private Integer id;
private String nom;
...
public Eleve(String nom, String prenom, Integer annee, Sexe sexe, Date dateNaissance, String adresse) {
...
}
public Eleve(Integer id, String nom, String prenom, Integer annee, Sexe sexe, Date dateNaissance, String adresse) {
this(nom, prenom, annee, sexe, dateNaissance, adresse);
this.id = id;
}
...Ici, on ne se contente pas de modifier le constructeur déjà existant afin de conserver une compatibilité avec le code de lecture des fichiers CSV.
III-B. Créer un DAO vide▲
Durée estimée : 30 secondes.
Sur le même modèle (sans jeu de mots) que CsvEleveDao, nous allons créer le DAO DbEleveDao (vide pour l'instant) pour charger les données depuis MySQL.
package com.thi.notes.dao;
import java.util.ArrayList;
import java.util.List;
import com.thi.notes.domain.Eleve;
public class DbEleveDao implements EleveDao {
@Override
public List<Eleve> findEleves() {
List<Eleve> eleves = new ArrayList<Eleve>();
// rien pour l'instant...
return eleves;
}
}Plus tard, on utilisera ce nouveau DAO de la manière suivante.
EleveDao eleveDao2 = new DbEleveDao();
List<Eleve> eleves2 = eleveDao2.findEleves();III-C. Ajouter le driver MySQL▲
Durée estimée : 30 secondes.
Nous devons aussi ajouter une dépendance vers le « driver » MySQL à notre projet. Pour cela, il faut ajouter le bloc suivant au fichier de configuration « pom.xml » de Maven.
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.18</version>
</dependency>Il faut ensuite relancer une installation Maven à l'aide de la commande suivante.
mvn clean install eclipse:eclipseEn fonction des éléments déjà présents sur l'ordinateur, la réponse de Maven devrait ressembler à la trace suivante.
C:\dvp\notes>mvn clean install eclipse:eclipse
[INFO] Scanning for projects...
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] Building Notes 1.0-SNAPSHOT
[INFO] ------------------------------------------------------------------------
[INFO]
[INFO] --- maven-clean-plugin:2.4.1:clean (default-clean) @ notes ---
[INFO] Deleting C:\dvp\notes\target
[INFO]
...
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 10.704s
[INFO] Finished at: Wed Jan 25 20:36:01 CET 2012
[INFO] Final Memory: 13M/60M
[INFO] ------------------------------------------------------------------------
C:\dvp\notes>Il faut aussi rafraîchir le workspace d'Eclipse (touche F5). On constate que plusieurs librairies sont apparues.
III-D. Lecture des données▲
Durée estimée : 3 minutes.
On entre dans le vif du sujet. Comme indiqué plus haut, nous allons donc lire la liste d'élèves, depuis la base de données MySQL.
On commence en créant quelques constantes, indiquant où trouver la base, son nom, le login de l'utilisateur créé plus haut ainsi que son mot de passe.
public class DbEleveDao implements EleveDao {
private static String URL = "jdbc:mysql://localhost:3306/notes_eleves";
private static String LOGIN = "dvp";
private static String PASSWORD = "hello";
...Dans un vrai projet, il faudra penser à mettre ces valeurs dans un fichier de configuration.
On peut maintenant créer une connexion à la base.
package com.thi.notes.dao;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.Connection;
...
public class DbEleveDao implements EleveDao {
private static String URL = "jdbc:mysql://localhost:3306/notes_eleves";
private static String LOGIN = "dvp";
private static String PASSWORD = "hello";
@Override
public List<Eleve> findEleves() {
List<Eleve> eleves = new ArrayList<Eleve>();
Connection con = DriverManager.getConnection(URL, LOGIN, PASSWORD);
...Dans un vrai projet, il vaut mieux éviter de créer une connexion à chaque requête, car c'est une opération longue et coûteuse. À la place on utilise un « gestionnaire de connexion » à qui on demande de nous fournir une connexion. Ce gestionnaire ouvre la (ou les) connexion(s) au lancement du programme pour qu'elle soit immédiatement disponible. Quand on n'a plus besoin de la connexion, on indique au gestionnaire qu'il peut la reprendre. Des serveurs comme Tomcat ou Websphere fournissent, sur le même principe, des « pools de connexion »…
Puis on peut lancer une requête vers la base, sans oublier de relâcher les ressources à la fin.
...
private final static String QUERY_FIND_ELEVES = "SELECT * FROM eleve ";
@Override
public List<Eleve> findEleves() {
List<Eleve> eleves = new ArrayList<Eleve>();
Connection con = DriverManager.getConnection(URL, LOGIN, PASSWORD);
Statement stmt = con.createStatement();
ResultSet rset = stmt.executeQuery(QUERY_FIND_ELEVES);
// ...
rset.close();
stmt.close();
con.close();
...Comme de nombreuses erreurs peuvent se produire, il faut gérer les exceptions.
@Override
public List<Eleve> findEleves() {
List<Eleve> eleves = new ArrayList<Eleve>();
Connection con = null;
Statement stmt = null;
try {
con = DriverManager.getConnection(URL, LOGIN, PASSWORD);
stmt = con.createStatement();
ResultSet rset = stmt.executeQuery(QUERY_FIND_ELEVES);
while (rset.next()) {
Eleve eleve = rsetToEleve(rset);
eleves.add(eleve);
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
if (stmt != null) {
try {
// Le stmt.close ferme automatiquement le rset.
stmt.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (con != null) {
try {
con.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
...On peut maintenant transformer les données, qu'on a sous forme de « Resultset », en objets Eleve. Pour cela, il faut parcourir le « resultset » comme suit.
...
con = DriverManager.getConnection(URL, LOGIN, PASSWORD);
stmt = con.createStatement();
rset = stmt.executeQuery(QUERY_FIND_ELEVES);
while ( rset.next() ) {
Integer id = rset.getInt("id");
String nom = rset.getString("nom");
String prenom = rset.getString("prenom");
Integer annee = rset.getInt("classe");
Date dateNaissance = rset.getDate("date_naissance");
String adresse = rset.getString("adresse");
String sexeStr = rset.getString("sexe");
Sexe sexe = Sexe.valueOf(sexeStr);
Eleve eleve = new Eleve(id, nom, prenom, annee, sexe, dateNaissance, adresse);
eleves.add(eleve);
}
...Il ne reste plus qu'à réorganiser un peu le code (pour éviter d'avoir des méthodes trop grosses) et à factoriser les traitements. Le code final, avec ces changements, est disponible en annexes.
En annexes, je montre également comment lancer une requête avec des paramètres, à l'aide d'un PreparedStatement.
IV. Conclusions▲
Ce petit document le prouve ; il est très simple de charger une liste depuis une base de données comme MySQL. On aurait pu réaliser les mêmes choses avec d'autres bases en ne changeant presque rien dans le code. Durant ce tutoriel, nous n'avons vu qu'une infime partie des fonctionnalités de JDBC, mais l'essentiel, pour bien débuter, est là.
Le code final de ce tutoriel est disponible dans le fichier ZIP notes5_domain-et-dao-csv-db.zip.code final
Retrouvez les tutoriels de la série « en 5 minutes » sur developpez.com à l'adresse https://thierry-leriche-dessirier.developpez.com/#page_articlesEn 5 minutes
V. Remerciements▲
Je tiens à remercier, en tant qu'auteur de tutoriel rapide, toutes les personnes qui m'ont aidé et soutenu. Je pense tout d'abord à mes collègues qui subissent mes questions au quotidien, mais aussi à mes contacts et amis du web, dans le domaine de l'informatique ou non, qui m'ont fait part de leurs remarques et critiques. Bien entendu, je n'oublie pas l'équipe de developpez.com qui m'a guidé dans la rédaction de cet article et m'a aidé à le corriger et le faire évoluer, principalement sur le forum.
Plus particulièrement j'adresse mes remerciements, à Gueritarish, Mickael BARON (keulkeul) et Maxime Gault (_Max_)

VI. Annexes▲
VI-A. Liens▲
La FAQ de JDBC est disponible sur developpez.com à l'adresse suivante
https://java.developpez.com/faq/jdbc/?page=generalites#definitionJDBC
Pour suivre cet article, je conseille la lecture des tutoriels
« Importer un projet Maven dans Eclipse en 5 minutes » disponible à l'adresse
https://thierry-leriche-dessirier.developpez.com/tutoriels/java/importer-projet-maven-dans-eclipse-5-minImporter un projet Maven dans Eclipse en 5 minutes
et « Charger des données depuis un fichier CSV simple en 5 minutes » à l'adresse
https://thierry-leriche-dessirier.developpez.com/tutoriels/java/charger-donnees-fichier-csv-5-min/
Pour aller plus loin, je vous recommande en particulier la lecture du tutoriel « Mapper sa base de données avec le pattern DAO » de Cyrille Herby, disponible sur developpez.com à l'adresse
https://cyrille-herby.developpez.com/tutoriels/java/mapper-sa-base-donnees-avec-pattern-daoMapper sa base de données avec le pattern DAO
VI-B. Création de la base avec phpMyAdmin en image▲
VI-C. Les fichiers importants en entier▲
package com.thi.notes.dao;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import com.thi.notes.domain.Eleve;
import com.thi.notes.domain.Sexe;
public class DbEleveDao implements EleveDao {
/**
* URL de connection
*/
private final static String URL = "jdbc:mysql://localhost:3306/notes_eleves";
/**
* Nom du user
*/
private final static String LOGIN = "dvp";
/**
* Mot de passe du user
*/
private final static String PASSWORD = "hello";
/**
* SQL de recherche de la liste des eleves.
*/
private final static String QUERY_FIND_ELEVES = "SELECT * FROM eleve ";
/**
* SQL de recherche de la liste des eleves par classe.
*/
private final static String QUERY_FIND_ELEVES_BY_CLASSE = "SELECT * FROM eleve WHERE classe = ? ";
private Connection getConnexion() throws SQLException {
final Connection con = DriverManager.getConnection(URL, LOGIN, PASSWORD);
return con;
}
@Override
public List<Eleve> findEleves() {
List<Eleve> eleves = new ArrayList<Eleve>();
Connection con = null;
Statement stmt = null;
try {
con = getConnexion();
stmt = con.createStatement();
final ResultSet rset = stmt.executeQuery(QUERY_FIND_ELEVES);
while (rset.next()) {
final Eleve eleve = rsetToEleve(rset);
eleves.add(eleve);
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
if (stmt != null) {
try {
// Le stmt.close ferme automatiquement le rset
stmt.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (con != null) {
try {
con.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
return eleves;
}
@Override
public List<Eleve> findElevesByClasse(Integer classe) {
List<Eleve> eleves = new ArrayList<Eleve>();
Connection con = null;
PreparedStatement stmt = null;
try {
con = getConnexion();
stmt = con.prepareStatement(QUERY_FIND_ELEVES_BY_CLASSE);
stmt.setInt(1, classe);
final ResultSet rset = stmt.executeQuery();
while (rset.next()) {
final Eleve eleve = rsetToEleve(rset);
eleves.add(eleve);
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
if (stmt != null) {
try {
// Le stmt.close ferme automatiquement le rset
stmt.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (con != null) {
try {
con.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
return eleves;
}
private Eleve rsetToEleve(final ResultSet rset) throws SQLException {
final Integer id = rset.getInt("id");
final String nom = rset.getString("nom");
final String prenom = rset.getString("prenom");
final Integer annee = rset.getInt("classe");
final Date dateNaissance = rset.getDate("date_naissance");
final String adresse = rset.getString("adresse");
final String sexeStr = rset.getString("sexe");
final Sexe sexe = Sexe.valueOf(sexeStr);
final Eleve eleve = new Eleve(id, nom, prenom, annee, sexe, dateNaissance, adresse);
return eleve;
}
}J'attire votre attention sur le bloc suivant, correspondant à la recherche par critère à l'aide d'un PreparedStatement.
public class DbEleveDao implements EleveDao {
...
private final static String QUERY_FIND_ELEVES_BY_CLASSE = "SELECT * FROM eleve WHERE classe = ? ";
@Override
public List<Eleve> findElevesByClasse(Integer classe) {
List<Eleve> eleves = new ArrayList<Eleve>();
Connection con = null;
PreparedStatement stmt = null;
try {
con = getConnexion();
stmt = con.prepareStatement(QUERY_FIND_ELEVES_BY_CLASSE);
stmt.setInt(1, classe);
final ResultSet rset = stmt.executeQuery();
...package com.thi.notes;
import java.util.List;
import java.util.Map;
import com.thi.notes.dao.CsvEleveDao;
import com.thi.notes.dao.CsvNoteDao;
import com.thi.notes.dao.DbEleveDao;
import com.thi.notes.dao.EleveDao;
import com.thi.notes.dao.NoteDao;
import com.thi.notes.domain.Eleve;
public class DaoLauncher {
public static void main(String[] args) {
final EleveDao eleveDao = new CsvEleveDao();
final List<Eleve> eleves = eleveDao.findEleves();
System.out.println("Liste des eleves");
for (Eleve eleve : eleves) {
System.out.println(eleve);
}
final NoteDao noteDao = new CsvNoteDao();
final Map<String, Double> notes = noteDao.findNotesDernierExam();
System.out.println();
System.out.println("Notes des eleves");
for (String key : notes.keySet()) {
final Double note = notes.get(key);
System.out.println(key + " : " + note);
}
final EleveDao eleveDao2 = new DbEleveDao();
final List<Eleve> eleves2 = eleveDao2.findEleves();
System.out.println();
System.out.println("Liste des eleves en base");
for (Eleve eleve : eleves2) {
System.out.println(eleve);
}
final EleveDao eleveDao2 = new DbEleveDao();
final List<Eleve> eleves2 = eleveDao2.findEleves();
System.out.println();
System.out.println("Liste des eleves en base");
for (Eleve eleve : eleves2) {
System.out.println(eleve);
}
}
}










