Présentation du tutoriel
L’objectif de ce tutoriel est de réaliser une application "Todo List" en langage Java avec une interface graphique écrite en Swing. Ce tutoriel est destiné aux développeurs qui débutent en Java.
Caractéristiques techniques
-
Langages utilisés : Java et SQL
-
Outil de développement : NetBeans 6.8
-
Interface graphique : API Swing
-
Base de données : MySQL
-
Tests : JUnit
Description de l’application
Le but de cette application est de créer des tâches qui sont assignées à un utilisateur.
La première fonctionnalité consiste donc à identifier l’utilisateur via un écran de login. Cependant, la première fois que l’application est utilisée, il n’y a pas d’utilisateur enregistré en base de données. Il faut donc prévoir un écran qui permet l’enregistrement d’un utilisateur. Une fois connecté, l’utilisateur pourra visualiser la liste de toutes les tâches enregistrées, y compris celles des autres utilisateurs. A partir de cette liste, il pourra aussi ajouter une nouvelle tâche ou modifier une tâche existante.
Organisation du développement
Nous commencerons par bâtir la couche métier de l’application, i.e. :
-
les classes d’entités
-
les classes DAO pour la persistance
-
les classes de services pour la mise en oeuvre des règles de gestion
Pour tester le code produit dans la couche métier, nous utiliserons JUnit.
Nous développerons ensuite l’interface graphique.
Installation
Pour pouvoir réaliser ce tutorial, vous devez installer :
-
Un JDK
-
NetBeans
-
MySQL
Vous devez par ailleurs télécharger le fichier mysql-connector-java-5.X.X-bin.jar, qui nous permettra de nous connecter à MySQL depuis Java.
Il faut ensuite passer le script SQL suivant pour créer la base de données :
create database todolist default character set latin1 collate latin1_swedish_ci;
use todolist;
grant all privileges on todolist.* to scott@127.0.0.1 identified by 'tiger';
create table utilisateur (
identifiant varchar(10) not null,
mot_de_passe varchar(10) not null,
nom varchar(30) not null,
prenom varchar(30) not null,
primary key(identifiant)) type=InnoDB;
create table tache (
id integer not null auto_increment,
libelle varchar(100) not null,
date_fin date not null,
priorite int(11) not null,
utilisateur_id varchar(10),
primary key(id)) type=InnoDB;
alter table tache
add constraint fk_tache_utilisateur foreign key (utilisateur_id) references utilisateur (identifiant);
Développement de la couche métier
Création du projet
Ouvrir Netbeans et aller dans le menu File → New Project. Sélectionner la catégorie Java, puis le type de projet Java Class Library.
Cliquer ensuite sur le bouton Next et renseigner le nom de projet TodoListService, et cliquer enfin sur le bouton Finish :
Création des classes d’entité
Une application Java est composée d’un ensemble de classes qui définissent chacunes une petite partie de l’application. A terme, l’application pourra comporter un grand nombre de classes. A des fins de lisibilités, et aussi pour éviter des conflits de nommage, les classes ne doivent pas être toutes créées au même endroit. Il faut donc les organiser dans des packages, notion objet qui correspond à peu près à une arborescence de répertoires. Commençons donc par créer un package pour nos classes d’entités :
-
Dans le projet TodoListService, sélectionner Source Packages, puis par clic droit, aller dans le menu New → Java Package….
-
Rentrer le nom de package info.jtips.model (attention de bien respecter la casse !)
-
Cliquer enfin sur Finish
Votre package doit maintenant apparaître dans votre projet comme ci-dessous :
Nous pouvons maintenant créer nos classes d’entités, qui correspondent plus ou moins aux tables définies en base de données. Les classes à créer sont les suivantes :
-
Utilisateur
-
Tache
Pour cela, sélectionner le package créé précédemment, et par clic droit, sélectionner le menu New → Java Class… et entrer le nom de classe Utilisateur (encore une fois, faites bien attention à la casse !).
Cliquer sur le bouton Finish. Vous devriez obtenir ceci :
Notez que le nom du package est spécifié dans le code généré, ainsi bien sûr que le nom de la classe. Cette classe que nous venons de créer est aussi un nouveau type que nous pourrons utiliser par la suite.
Dans la classe Utilisateur, ajouter le code suivant qui consiste à définir les propriétés d’un utilisateur :
package info.jtips.model;
public class Utilisateur {
private String identifiant;
private String nom;
private String prenom;
private String motDePasse;
public String getIdentifiant() {
return identifiant;
}
public void setIdentifiant(String identifiant) {
this.identifiant = identifiant;
}
public String getMotDePasse() {
return motDePasse;
}
public void setMotDePasse(String motDePasse) {
this.motDePasse = motDePasse;
}
public String getNom() {
return nom;
}
public void setNom(String nom) {
this.nom = nom;
}
public String getPrenom() {
return prenom;
}
public void setPrenom(String prenom) {
this.prenom = prenom;
}
}
Chaque utilisateur est caractérisé par un identifiant, un nom, un prénom et un mot de passe. Ces informations sont de type chaîne de caractères, soit String en syntaxe Java. Par convention, pour définir une propriété au sens Java, il faut aussi ajouter les méthodes getters et setters. Ces méthodes doivent respecter les règles de nommage qui apparaissent dans le code ci-dessus.
De la même façon, vous pouvez créer la classe Tache :
package info.jtips.model;
import java.util.Date;
public class Tache {
private int id;
private String libelle;
private Date dateFin;
private int priorite;
public Date getDateFin() {
return dateFin;
}
public void setDateFin(Date dateFin) {
this.dateFin = dateFin;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getLibelle() {
return libelle;
}
public void setLibelle(String libelle) {
this.libelle = libelle;
}
public int getPriorite() {
return priorite;
}
public void setPriorite(int priorite) {
this.priorite = priorite;
}
}
Remarques sur le code ci-dessus :
-
Les champs
id
etpriorite
sont de type entier, soitint
en Java qui est un des 8 types primitifs (tout les autres types sont des classes) -
Le champ
dateFin
est du typejava.util.Date
, d’où la ligneimport.util.Date
entre la déclaration du package et la déclaration de la classe (la classeString
fait partie du packagejava.lang
qui est le seul package Java ne nécessitant pas d’import)
Nous venons de définir les entités Utilisateur
et Tache
, ainsi que leur propriétés.
En fait, ces classes ne sont pas indépendantes, elles sont liées de la façon suivante :
-
Une tâche est assignée à un seul utilisateur
-
Un utilisateur possède plusieurs tâches
En notation UML, cela donne le schéma suivant :
Les relations entre classes qui apparaissent sur le schéma ci-dessus s’appellent associations. En Java, une association se code comme une propriété :
-
Association Tache → Utilisateur (association dite many-to-one)
package info.jtips.model;
import java.util.Date;
public class Tache {
private int id;
private String libelle;
private Date dateFin;
private int priorite;
private Utilisateur utilisateur;
public Utilisateur getUtilisateur() {
return utilisateur;
}
public void setUtilisateur(Utilisateur utilisateur) {
this.utilisateur = utilisateur;
}
// autres getters et setters...
}
-
Association Utilisateur → Tache (association dite one-to-many)
package info.jtips.model;
import java.util.List;
public class Utilisateur {
private String identifiant;
private String nom;
private String prenom;
private String motDePasse;
private List<Tache> taches;
public List<Tache> getTaches() {
return taches;
}
public void setTaches(List<Tache> taches) {
this.taches = taches;
}
// autres getters et setters...
}
Remarque sur le code ci-dessus :
-
Comme un utilisateur possède plusieurs tâches, on définit l’association à l’aide d’un objet conteneur de type
java.util.List
-
Ce dernier type fait partie du package
java.util
, d’où l’import -
La liste contient des objets de type
Tache
: c’est ce que signifie la déclarationList<Tache>
(on appelle cela un générique)
Pour terminer avec les classes d’entités, nous allons coder une méthode nommée ajouterTache
qui permet d’ajouter une tâche à un utilisateur :
package info.jtips.model;
import java.util.List;
public class Utilisateur {
private String identifiant;
private String nom;
private String prenom;
private String motDePasse;
private List<Tache> taches;
public void ajouterTache(Tache t) {
if (taches == null) taches = new ArrayList<Tache>();
taches.add(t);
t.setUtilisateur(this);
}
// getters et setters...
}
Remarques sur le code de la méthode ajouterTache(Tache t)
ci-dessus :
-
Déclaration de la méthode
-
La méthode prend en paramètre une variable de type
Tache
-
Cette méthode ne retourne pas de résultat : d’où le type
void
pour le retour
-
-
1ère ligne de la méthode
-
Les variables ne sont pas des objets, elles "pointent" sur des objets (on parle aussi d'instance pour désigner un objet)
-
La variable
taches
pointe d’abord sur l’objetnull
, car on ne lui a pas affecté d’objet -
L’opérateur
new
permet de créer (ou instancier) un objet -
Dans la méthode
ajouterTache(Tache t)
, avant d’ajouter la tâche à la liste, on vérifie si la liste est nulle et on l’instancie si c’est le cas (notez que l’instanciation ne se produira qu’une seule fois, i.e. lors du premier appel à la méthodeajouterTache(Tache t)
)
-
-
2ème ligne de la méthode
-
On ajoute l’objet passé en paramètre à la liste des tâches de l’utilisateur. Comme vous pouvez le constater, l’appel à une méthode est effectué via la notation pointée.
-
-
3ème ligne de la méthode
-
L’association est bi-directionnelle : la nouvelle tâche est connue de l’utilisateur, mais il faut aussi renseigner la tâche afin qu’elle connaisse l’utilisateur
-
L’utilisateur est renseigné auprès de la tâche par appel de la méthode
setUtilisateur(Utilisateur u)
-
La variable implicite
this
référence l’instance en cours. En l’occurence il s’agit de l’utilisateur que l’on souhaite passer à la tâche
-
Création des classes DAO
Les DAO sont des classes d’accès aux données. Il s’agit de classes qui fournissent des méthodes dont le but est d’exécuter des requêtes SQL. De plus, ces méthodes sont chargées de la traduction des données tabulaires en objet et réciproquement. Une bonne pratique pour mettre en oeuvre les DAO consiste à d’abord spécifier les méthodes qu’elles doivent mettre en oeuvre. A cette fin, nous allons créer des types Java qui ne sont pas des classes, mais des interfaces (à la différence d’une classe, une interface n’est pas instanciable).
Dans votre projet NetBeans, créer le package info.jtips.dao
et créer l’interface nommée UtilisateurDao
(menu New → Java Interface…).
Vous ajouterez aussi le code de spécification des méthodes qui figurent ci-dessous.
package info.jtips.dao;
import info.jtips.model.Utilisateur;
public interface UtilisateurDao {
Utilisateur findById(String identifiant);
void create(Utilisateur u);
}
Remarque : les méthodes créées ci-dessus ne possèdent pas de corps pour ajouter du code; nous avons simplement défini la signature des différentes méthodes
De la même façon, créer l’interface TacheDao
:
package info.jtips.dao;
import info.jtips.model.Tache;
import java.util.List;
public interface TacheDao {
List<Tache> findAll();
Tache findById(int id);
void create(Tache t);
void update(Tache t);
void delete(Tache t);
}
Pour continuer, ajoutons la classe UtilisateurDaoImpl
dans le package info.jtips.dao.impl
:
package info.jtips.dao.impl;
import info.jtips.dao.UtilisateurDao;
import info.jtips.model.Utilisateur;
public class UtilisateurDaoImpl implements UtilisateurDao {
public Utilisateur findById(String identifiant) {
return null;
}
public void create(Utilisateur u) {
}
}
Cette classe réalise l’interface que nous avons définie précédemment :
-
C’est ce qu signifie le mot clé
implements
-
La classe redéfinit les méthodes spécifiées par l’interface (cette fois-ci, nous pouvons ajouter du code dans les méthodes)
Voyons maintenant comment coder cette classe de telle sorte que l’on puisse mettre en oeuvre le requêtage SQL nécessaire. Nous allons pour cela utiliser l’API JDBC, qui consiste à :
-
établir une connexion sur la base de données
-
récupérer un statement qui permettra d’exécuter une requête SQL
-
récupérer un result set en cas de SELECT SQL, afin de lire les données retournées par la base
Commençons par créer une méthode pour établir la connexion sur la base de données (MySQL en l’occurence) :
package info.jtips.dao.impl;
import info.jtips.dao.UtilisateurDao;
import info.jtips.model.Utilisateur;
import java.sql.Connection;
import java.sql.DriverManager;
public class UtilisateurDaoImpl implements UtilisateurDao {
public Utilisateur findById(String identifiant) {
return null;
}
public void create(Utilisateur u) {
}
private Connection getConnexion() throws Exception {
// Chargement du driver
Class.forName("com.mysql.jdbc.Driver");
// Obtention de la connexion
String url = "jdbc:mysql://localhost:3306/todolist";
Connection cx = DriverManager.getConnection(url, "scott", "tiger");
return cx;
}
}
Remarque sur le code de la méthode getConnexion()
:
-
Toutes les classes JDBC (ici
Connection
etDriverManager
) font partie du packagejava.sql
-
Déclaration de la méthode
-
C’est une méthode marquée
private
, c’est à dire que seule la classeUtilisateurDaoImpl
peut l’invoquer (contrairement aux autres méthodes qui sont marquéespublic
, et peuvent donc être invoquées depuis n’importe quelle autre classe) -
Cette méthode est susceptible de générer une exception (par exemple si la base de données n’est pas démarrée) : d’où le
throws Exception
-
La méthode retourne une instance de type
Connection
-
-
1ère ligne de code
-
Permet de charger le driver correspondant à MySQL
-
Ce driver est fourni dans un fichier .jar que nous installerons par la suite
-
-
2ème ligne de code
-
Définition de l’URL de connexion sur la base de données MySQL nommée todolist
-
-
3ème ligne de code
-
On établit la connexion avec l’URL, l’identifiant scott et le mot de passe tiger
-
-
Dernière ligne de code
-
Le mot clé
return
permet de retourner l’instance de la connexion établie
-
Nous pouvons maintenant coder la méthode findById()
:
package info.jtips.dao.impl;
import info.jtips.dao.UtilisateurDao;
import info.jtips.model.Utilisateur;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
public class UtilisateurDaoImpl implements UtilisateurDao {
public Utilisateur findById(String identifiant) {
Connection cx = null;
PreparedStatement st = null;
ResultSet rs = null;
Utilisateur utilisateur = null;
try {
cx = getConnexion();
st = cx.prepareStatement("select nom, prenom, mot_de_passe from utilisateur where identifiant = ?");
st.setString(1, identifiant);
rs = st.executeQuery();
if (rs.next()) {
String nom = rs.getString(1);
String prenom = rs.getString(2);
String motDePasse = rs.getString(3);
utilisateur = new Utilisateur();
utilisateur.setIdentifiant(identifiant);
utilisateur.setNom(nom);
utilisateur.setPrenom(prenom);
utilisateur.setMotDePasse(motDePasse);
}
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
try { if (rs != null) rs.close(); } catch (SQLException sqle) {}
try { if (st != null) st.close(); } catch (SQLException sqle) {}
try { if (cx != null) cx.close(); } catch (SQLException sqle) {}
}
return utilisateur;
}
public void create(Utilisateur u) {}
private Connection getConnexion() throws Exception {
// Chargement du driver
Class.forName("com.mysql.jdbc.Driver");
// Obtention de la connexion
String url = "jdbc:mysql://localhost:3306/todolist";
Connection cx = DriverManager.getConnection(url, "scott", "tiger");
return cx;
}
}
Remarques sur le code de la méthode findById()
:
-
La connexion JDBC est récupérée par appel à la méthode
getConnexion()
-
A partir de la connexion, on récupère un objet de type
java.sql.PreparedStatement
-
Le statement est initialisé avec une chaîne de requête SQL
-
La chaîne de requête SQL est paramétrée avec le caractère
?
de façon à pouvoir rechercher l’utilisateur qui correspond à l’identifiant passé en paramètre de la méthode -
Le paramètre
identifiant
est passé au statement via le codest.setString(1, identifiant)
(le premier paramètre désigne la position du paramètre injecté dans la chaîne de requête, ici la valeur 1) -
La requête est enfin exécutée suite à l’invocation de la méthode
st.executeQuery()
-
Après l’exécution de la requête, on récupère un objet de type
java.sql.ResultSet
, puisqu’il s’agit d’une requête de lecture -
Ce result set contient les données tabulaires lues dans la base de données, càd à priori plusieurs lignes, à l’instar d’une requête SQL SELECT
-
La méthode
rs.next()
permet de déplacer la position du result set sur le prochain enregistrement lu s’il existe, auquel cas la méthode retourne la valeur booléeenetrue
-
Dans notre cas, comme la recherche est effectuée sur la clé primaire, nous savons qu’il n’y aura qu’un seul résultat : il suffit donc d’itérer au plus une fois, d’où l’utilisation de la condition
if (rs.next())
-
Pour chaque ligne lue, les valeurs de colonne sont récupérées à l’aide des méthodes
rs.getXXX(index)
, oùXXX
correspond au type récupéré, etindex
à la position de la colonne dans la requête (on commence à partir de 1) -
Les données tabulaire sont ensuite transformées en objet, dans notre cas en objet
Utilisateur
-
D’où l’instanciation de l’utilisateur à retourner
-
Les setters de l’utilisateur sont invoqués pour passer les valeurs de propriétés récupérées depuis les result set
-
Le bloc
try..catch..finally
permet de gérer les exceptions -
Le bloc
try
contient le code d’exécution normale -
Le bloc
catch
est invoqué lorsqu’une erreur survient dans le bloctry
-
Dans notre exemple, nous propageons simplement l’exception à la méthode appelante sous la forme d’une
RuntimeException
(cette façon de faire est discutable, mais elle nous simplifie la tâche pour l’instant) -
Le bloc
finally
est invoqué quelle que soit la situation : erreur ou pas -
Dans notre cas, nous en profitons pour fermer les ressources ouvertes pour le traitement JDBC par invocation des méthodes
close()
sur les objetsConnection
,PreparedStatement
etResultSet
) -
Comme les méthodes
close()
génèrent elles aussi des exceptions, nous encapsulons le code de fermeture des ressources dans un bloctry..catch
Pour terminer le code de cette première DAO, il nous reste à compléter la méthode create(Utilisateur u)
. Contrairement à la méthode précédente, il s’agit d’une requête d’écriture; le code est donc légèrement différent :
-
Invocation de la méthode
st.executeUpdate()
plutôt quest.executeQuery()
-
Pas d’objet
ResultSet
à exploiter
public void create(Utilisateur u) {
Connection cx = null;
PreparedStatement st = null;
try {
cx = getConnexion();
st = cx.prepareStatement("insert into utilisateur (identifiant, nom, prenom, mot_de_passe) values (?, ?, ?, ?)");
st.setString(1, u.getIdentifiant());
st.setString(2, u.getNom());
st.setString(3, u.getPrenom());
st.setString(4, u.getMotDePasse());
st.executeUpdate();
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
try { if (st != null) st.close(); } catch (SQLException sqle) {}
try { if (cx != null) cx.close(); } catch (SQLException sqle) {}
}
}
De la même façon, nous codons la classe DAO pour les tâches :
package info.jtips.dao.impl;
import info.jtips.dao.TacheDao;
import info.jtips.model.Tache;
import info.jtips.model.Utilisateur;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
public class TacheDaoImpl implements TacheDao {
public List<Tache> findAll() {
Connection cx = null;
PreparedStatement st = null;
ResultSet rs = null;
List<Tache> taches = new ArrayList<Tache>();
try {
cx = getConnexion();
st = cx.prepareStatement("select id, libelle, priorite, date_fin, utilisateur_id from tache");
rs = st.executeQuery();
while (rs.next()) {
int id = rs.getInt(1);
String libelle = rs.getString(2);
int priorite = rs.getInt(3);
Date dateFin = rs.getDate(4);
String utilisateurId = rs.getString(5);
Tache t = new Tache();
t.setId(id);
t.setLibelle(libelle);
t.setDateFin(dateFin);
t.setPriorite(priorite);
Utilisateur u = new Utilisateur();
u.setIdentifiant(utilisateurId);
t.setUtilisateur(u);
taches.add(t);
}
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
try { if (rs != null) rs.close(); } catch (SQLException sqle) {}
try { if (st != null) st.close(); } catch (SQLException sqle) {}
try { if (cx != null) cx.close(); } catch (SQLException sqle) {}
}
return taches;
}
public Tache findById(int id) {
Connection cx = null;
PreparedStatement st = null;
ResultSet rs = null;
Tache t = null;
try {
cx = getConnexion();
st = cx.prepareStatement("select id, libelle, priorite, date_fin, utilisateur_id from tache where id = ?");
st.setInt(1, id);
rs = st.executeQuery();
if (rs.next()) {
String libelle = rs.getString(2);
int priorite = rs.getInt(3);
Date dateFin = rs.getDate(4);
String utilisateurId = rs.getString(5);
t = new Tache();
t.setId(id);
t.setLibelle(libelle);
t.setDateFin(dateFin);
t.setPriorite(priorite);
Utilisateur u = new Utilisateur();
u.setIdentifiant(utilisateurId);
t.setUtilisateur(u);
}
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
try { if (rs != null) rs.close(); } catch (SQLException sqle) {}
try { if (st != null) st.close(); } catch (SQLException sqle) {}
try { if (cx != null) cx.close(); } catch (SQLException sqle) {}
}
return t;
}
public List<Tache> findByUtilisateur(Utilisateur u) {
Connection cx = null;
PreparedStatement st = null;
ResultSet rs = null;
List<Tache> taches = new ArrayList<Tache>();
try {
cx = getConnexion();
st = cx.prepareStatement("select id, libelle, priorite, date_fin from tache where utilisateur_id = ?");
st.setString(1, u.getIdentifiant());
rs = st.executeQuery();
while (rs.next()) {
int id = rs.getInt(1);
String libelle = rs.getString(2);
int priorite = rs.getInt(3);
Date dateFin = rs.getDate(4);
Tache t = new Tache();
t.setId(id);
t.setLibelle(libelle);
t.setDateFin(dateFin);
t.setPriorite(priorite);
t.setUtilisateur(u);
taches.add(t);
}
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
try { if (rs != null) rs.close(); } catch (SQLException sqle) {}
try { if (st != null) st.close(); } catch (SQLException sqle) {}
try { if (cx != null) cx.close(); } catch (SQLException sqle) {}
}
return taches;
}
public void create(Tache t) {
Connection cx = null;
PreparedStatement st = null;
try {
cx = getConnexion();
st = cx.prepareStatement("insert into tache (libelle, priorite, date_fin, utilisateur_id) values (?, ?, ?, ?)");
st.setString(1, t.getLibelle());
st.setInt(2, t.getPriorite());
Date df = t.getDateFin();
java.sql.Date dateFin = new java.sql.Date(df.getTime());
st.setDate(3, dateFin);
st.setString(4, t.getUtilisateur().getIdentifiant());
st.executeUpdate();
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
try { if (st != null) st.close(); } catch (SQLException sqle) {}
try { if (cx != null) cx.close(); } catch (SQLException sqle) {}
}
}
public void update(Tache t) {
Connection cx = null;
PreparedStatement st = null;
try {
cx = getConnexion();
st = cx.prepareStatement("update tache set libelle = ?, priorite = ?, date_fin = ?, utilisateur_id = ? where id = ?");
st.setString(1, t.getLibelle());
st.setInt(2, t.getPriorite());
st.setDate(3, new java.sql.Date(t.getDateFin().getTime()));
st.setString(4, t.getUtilisateur().getIdentifiant());
st.setInt(5, t.getId());
st.executeUpdate();
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
try { if (st != null) st.close(); } catch (SQLException sqle) {}
try { if (cx != null) cx.close(); } catch (SQLException sqle) {}
}
}
public void delete(Tache t) {
Connection cx = null;
PreparedStatement st = null;
try {
cx = getConnexion();
st = cx.prepareStatement("delete from tache where id = ?");
st.setInt(1, t.getId());
st.executeUpdate();
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
try { if (st != null) st.close(); } catch (SQLException sqle) {}
try { if (cx != null) cx.close(); } catch (SQLException sqle) {}
}
}
private Connection getConnexion() throws Exception {
// Chargement du driver
Class.forName("com.mysql.jdbc.Driver");
// Obtention de la connexion
String url = "jdbc:mysql://localhost:3306/todolist";
Connection cx = DriverManager.getConnection(url, "scott", "tiger");
return cx;
}
}
Tester les DAO avec JUnit
Nos DAO sont prêtes, nous allons maintenant les tester avec JUnit. Avant toute chose, il faut installer le driver MySQL, faute de quoi, nous ne pourrons pas nous connecter à la base de données. Pour cela, il suffit de sélectionner le menu Add JAR/Folder… par clic droit sur le répertoire Librairies de votre projet :
Après la sélection du driver MySQL, vous devriez le voir apparaître dans votre explorateur de projet sous NetBeans :
Ajoutons maintenant la classe de test JUnit pour la DAO Utilisateur :
-
Dans le répertoire Test Packages de votre projet, ajouter le package
info.jtips.dao
-
Sélectionner le menu New → Other… par clic droit sur le package que vous venez de créer
-
Choisir le type JUnit Test dans la catégorie JUnit
Rentrer ensuite le nom classe de la classe de test : UtilisateurDaoTest
, puis sélectionner les cases à cocher comme ci-dessous :
Choisir enfin JUnit 4 comme librairie de test :
Le code généré par NetBeans est le suivant :
package info.jtips.dao;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
import static org.junit.Assert.*;
public class UtilisateurDaoTest {
public UtilisateurDaoTest() {
}
@BeforeClass
public static void setUpClass() throws Exception {
}
@AfterClass
public static void tearDownClass() throws Exception {
}
}
Complétons cette classe afin de tester l’insertion d’un utilisateur en base de données en ajoutant la méthode testCreate()
:
package info.jtips.dao;
import info.jtips.dao.impl.UtilisateurDaoImpl;
import info.jtips.model.Utilisateur;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
import static org.junit.Assert.*;
public class UtilisateurDaoTest {
public UtilisateurDaoTest() {
}
@BeforeClass
public static void setUpClass() throws Exception {
}
@AfterClass
public static void tearDownClass() throws Exception {
}
@Test
public void testCreate() {
Utilisateur u = new Utilisateur();
u.setIdentifiant("bb");
u.setPrenom("Bugs");
u.setNom("Bunny");
u.setMotDePasse("mp");
UtilisateurDao dao = new UtilisateurDaoImpl();
dao.create(u);
System.out.println("Terminé");// permet d'afficher le message "Terminé" en console
}
}
Remarque sur le code de la classe de test :
-
La méthode
testCreate()
permet de tester la méthodecreate()
de la DAO utilisateur -
Cette méthode pour être exécutée avec JUnit est annotée avec
@Test
-
Bonne pratique de programmation Java pour le composant à tester :
-
Le type de la variable
dao
estUtilisateurDao
, ie l’interface qui spécifie les méthodes de la DAO -
L’objet pointé par
dao
est du typeUtilisateurDaoImpl
, ie la classe de réalisation de l’interface DAO
Pour exécuter ce code, sélectionner le menu Test File par clic droit sur la classe UtilisateurDaoTest
. Vous devriez obtenir le rapport JUnit suivant :
Vous pouvez aussi vérifier en base de données la création du nouvel enregistrement.
Pour terminer la classe de test, nous ajoutons la méthode testFindById()
:
package info.jtips.dao;
import info.jtips.dao.impl.UtilisateurDaoImpl;
import info.jtips.model.Utilisateur;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
import static org.junit.Assert.*;
public class UtilisateurDaoTest {
public UtilisateurDaoTest() {
}
@BeforeClass
public static void setUpClass() throws Exception {
}
@AfterClass
public static void tearDownClass() throws Exception {
}
@Test
public void testCreate() {
Utilisateur u = new Utilisateur();
u.setIdentifiant("bb");
u.setPrenom("Bugs");
u.setNom("Bunny");
u.setMotDePasse("mp");
UtilisateurDao dao = new UtilisateurDaoImpl();
dao.create(u);
System.out.println("Terminé");// permet d'afficher le message "Terminé" en console
}
@Test
public void testFindById() {
UtilisateurDao dao = new UtilisateurDaoImpl();
Utilisateur u = dao.findById("bb");
assertEquals("bb", u.getIdentifiant());
assertEquals("Bugs", u.getPrenom());
assertEquals("Bunny", u.getNom());
assertEquals("mp", u.getMotDePasse());
}
}
Remarque sur le code de la méthode testFindById()
:
-
Pour vérifier les résultat de la méthode, nous utilisons la méthode
assertEquals()
-
Cette méthode consiste à comparer une valeur attendue (1er argument), à un résultat (2ème argument)
-
Si le résultat est égal à la valeur attendue, alors l’exécution se poursuit, sinon une exception est levée et la méthode de test est interrompue
Si vous relancez l’exécution du test, vous devriez obtenir le résultat suivant :
Nous constatons que le test de la méthode findById()
a réussi (rapport vert), tandis que le test de la méthode create()
a échoué (rapport rouge). C’est normal, puisque nous avons tenté de créer un nouvel enregistrement avec une clé primaire déjà présente en base (enregistrement créé lors de l’exécution précédente).
Voici enfin la classe de test pour la DAO des tâches :
package info.jtips.dao;
import info.jtips.dao.impl.TacheDaoImpl;
import info.jtips.model.Tache;
import info.jtips.model.Utilisateur;
import java.util.Date;
import java.util.List;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
import static org.junit.Assert.*;
public class TacheDaoTest {
public TacheDaoTest() {
}
@BeforeClass
public static void setUpClass() throws Exception {
}
@AfterClass
public static void tearDownClass() throws Exception {
}
@Test
public void testCreate() {
Utilisateur u = new Utilisateur();
u.setIdentifiant("bb");
u.setPrenom("Bugs");
u.setNom("Bunny");
u.setMotDePasse("mp");
Tache t1 = new Tache();
t1.setLibelle("Une première tâche");
t1.setDateFin(new Date());
t1.setPriorite(1);
u.ajouterTache(t1);
Tache t2 = new Tache();
t2.setLibelle("Une deuxième tâche");
t2.setDateFin(new Date());
t2.setPriorite(1);
u.ajouterTache(t2);
TacheDao dao = new TacheDaoImpl();
dao.create(t1);
dao.create(t2);
System.out.println("Terminé");
}
@Test
public void testFindById() {
TacheDao dao = new TacheDaoImpl();
Tache t = dao.findById(1);
assertEquals(1, t.getId());
assertEquals("Une première tâche", t.getLibelle());
assertEquals("bb", t.getUtilisateur().getIdentifiant());
}
@Test
public void testFindAll() {
TacheDao dao = new TacheDaoImpl();
List<Tache> taches = dao.findAll();
assertEquals(2, taches.size());
}
@Test
public void testUpdate() {
TacheDao dao = new TacheDaoImpl();
Tache t = dao.findById(2);
t.setLibelle("Tâche modifiée");
dao.update(t);
}
@Test
public void testDelete() {
TacheDao dao = new TacheDaoImpl();
Tache t = dao.findById(1);
dao.delete(t);
}
}
Refactoring !
Après le développement des classes présentées ci-dessus, je me suis aperçu que je m’étais trompé dans les noms de package. En effet, il serait souhaitable d’ajouter le nom de l’application "TodoList" dans les noms de package. Nous allons donc utiliser la fonctionnalité de refactoring offerte par NetBeans. Pour cela, sélectionner le package à renommer, puis par clic droit, naviguer vers le menu Refactor → Rename… :
Nous renommons ainsi l’ensemble de nos packages, sans oublier les classes de test, suivant la règle de nommage info.jtips.todolist.xxx
, ce qui donne au final :
Création des classes de service métier
Cf article Partie 2 : écrire une couche de service métier.
Téléchargement du projet
Le projet NetBeans correspondant à cette première partie.
Création des classes de présentation (Swing)
Bientôt un nouvel article pour traiter le sujet…