Tutoriel Java

Cette page a été rédigée il y a fort fort longtemps, et n'a pas tellement été mise à jour.

 

Vous savez, moi je ne crois pas qu'il y ait de bonne ou de mauvaise page. Moi, si je devais résumer mon wiki aujourd'hui avec vous, je dirais que c'est d'abord des rencontres. Des gens qui m'ont tendu la main, peut-être à un moment où je ne pouvais pas, où j'étais seul chez moi. Et c'est assez curieux de se dire que les hasards, les rencontres forgent une destinée... Parce que quand on a le goût de la chose, quand on a le goût de la chose bien faite, le beau geste, parfois on ne trouve pas l'interlocuteur en face je dirais, le miroir qui vous aide à avancer. Alors ça n'est pas mon cas, comme je disais là, puisque moi au contraire, j'ai pu ; et je dis merci au wiki, je lui dis merci, je chante le wiki, je danse le wiki... je ne suis qu'amour ! Et finalement, quand des gens me disent « Mais comment fais-tu pour avoir cette humanité ? », je leur réponds très simplement que c'est ce goût de l'amour, ce goût donc qui m'a poussé aujourd'hui à entreprendre une construction logicielle... mais demain qui sait ? Peut-être simplement à me mettre au service de la communauté, à faire le don, le don de soi.

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.

TutJava1.png

Cliquer ensuite sur le bouton Next et renseigner le nom de projet TodoListService, et cliquer enfin sur le bouton Finish :

TutJava2.png

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 !)

TutJava3.png
  • Cliquer enfin sur Finish

Votre package doit maintenant apparaître dans votre projet comme ci-dessous :

TutJava4.png

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 !).

TutJava5.png

Cliquer sur le bouton Finish. Vous devriez obtenir ceci :

TutJava6.png

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 et priorite sont de type entier, soit int en Java qui est un des 8 types primitifs (tout les autres types sont des classes)

  • Le champ dateFin est du type java.util.Date, d’où la ligne import.util.Date entre la déclaration du package et la déclaration de la classe (la classe String fait partie du package java.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 :

TutJava7.png

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éclaration List<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’objet null, 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éthode ajouterTache(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 et DriverManager) font partie du package java.sql

  • Déclaration de la méthode

    • C’est une méthode marquée private, c’est à dire que seule la classe UtilisateurDaoImpl peut l’invoquer (contrairement aux autres méthodes qui sont marquées public, 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 code st.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éeene true

  • 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é, et index à 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 bloc try

  • 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 objets Connection, PreparedStatement et ResultSet)

  • Comme les méthodes close() génèrent elles aussi des exceptions, nous encapsulons le code de fermeture des ressources dans un bloc try..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 que st.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 :

TutJava8.png

Après la sélection du driver MySQL, vous devriez le voir apparaître dans votre explorateur de projet sous NetBeans :

TutJava9.png

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

TutJava10.png

Rentrer ensuite le nom classe de la classe de test : UtilisateurDaoTest, puis sélectionner les cases à cocher comme ci-dessous :

TutJava11.png

Choisir enfin JUnit 4 comme librairie de test :

TutJava12.png

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éthode create() 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 est UtilisateurDao, ie l’interface qui spécifie les méthodes de la DAO

  • L’objet pointé par dao est du type UtilisateurDaoImpl, 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 :

TutJava13.png

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 :

TutJava14.png

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…​ :

TutJava15.png

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 :

TutJava16.png

Création des classes 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…​