Présentation
Cet article consistue la deuxième partie du tutoriel Java, dont l’objectif est de réaliser une application "Todo List" en langage Java avec une interface graphique écrite en Swing. Dans la première partie, nous avons développé les classes model et dao. Nous allons maintenant écrire les classes service, qui vont notamment mettre en oeuvre les règles de gestion.
Création des classes de service métier
Nous allons continuer à travailler dans le projet TodoListService créé sous NetBeans dans la première partie du tutoriel. Pour rappel, ce projet va contenir à terme toute la couche métier de l’application.
Spécification du service
Commençons donc par créer l’interface TodoListService
que nous plaçons dans le package info.jtips.todolist.service
:
package info.jtips.todolist.service;
import info.jtips.todolist.model.Tache;
import info.jtips.todolist.model.Utilisateur;
import java.util.List;
public interface TodoListService {
Utilisateur verifierLogin(String id, String pwd);
void enregistrerUtilisateur(Utilisateur u);
Tache lireTache(int id);
List<Tache> lireListeTaches();
void sauvegarderTache(Tache t);
void supprimerTache(Tache t);
}
Cette interface représente le contrat du service métier, c’est à dire qu’elle se contente d’énoncer la liste des méthodes qui doivent être disponibles dans un service de ce type. Elle ne dit pas comment les méthodes doivent être mises en oeuvre, car cela est de la responsabilité des classes d’implémentation (voir un peu plus loin).
Par ailleurs, nous ajoutons une classe d’exception personnalisée dans le même package, car les méthodes du service métier seront susceptibles de lever un exception si une règle de gestion échoue :
package info.jtips.todolist.service;
public class TodoListException extends RuntimeException {
public TodoListException(Throwable cause) {
super(cause);
}
public TodoListException(String message, Throwable cause) {
super(message, cause);
}
public TodoListException(String message) {
super(message);
}
public TodoListException() {
}
}
j’ai choisi d’hériter de la classe
|
Implémentation du service
Ajoutons maintenant la classe d’implémentation de l’interface définie précédemment, à savoir la classe TodoListServiceImpl
placée dans le package info.jtips.todolist.service.impl
.
Cette implémentation s’appuie sur la couche DAO pour interroger et mettre à jour la base de données.
Les méthodes du service vont donc utiliser des instances de UtilisateurDao
et TacheDao
pour effectuer le requêtage SQL.
Comme les DAO sont utilisées dans toutes les méthodes du service, nous les déclarons en champs d’instance (en fait, on peut procéder de cette façon car les DAO sont dites stateless, c’est à dire qu’elles ne possèdent pas d’état propre à un objet client).
Dans le code ci-dessous, examiner les règles de gestion et notamment l’utilisation de l’exception personnalisée TodoListException
:
package info.jtips.todolist.service.impl;
import info.jtips.todolist.dao.TacheDao;
import info.jtips.todolist.dao.UtilisateurDao;
import info.jtips.todolist.dao.impl.TacheDaoImpl;
import info.jtips.todolist.dao.impl.UtilisateurDaoImpl;
import info.jtips.todolist.model.Tache;
import info.jtips.todolist.model.Utilisateur;
import info.jtips.todolist.service.TodoListException;
import info.jtips.todolist.service.TodoListService;
import java.util.List;
public class TodoListServiceImpl implements TodoListService {
private UtilisateurDao utilisateurDao = new UtilisateurDaoImpl();
private TacheDao tacheDao = new TacheDaoImpl();
/**
* Cette méthode permet de vérifier un couple "identifiant/mot de passe"
* issue d'un écran de login. Si l'utilisateur correspondant à l'identifiant
* ou si le mot de passe est incorrect, alors une exception est levée
*/
public Utilisateur verifierLogin(String id, String pwd) {
Utilisateur u = utilisateurDao.findById(id);
if (u != null && pwd != null && pwd.equals(u.getMotDePasse())) {
return u;// connexion OK
}
throw new TodoListException("Echec de l'authentification");
}
/**
* Un utilisateur peut être enregistré si son identifiant n'est pas déjà utilisé.
* Si l'identifiant est déjà utilisé, une exception est levée.
*/
public void enregistrerUtilisateur(Utilisateur u) {
// vérification login déjà utilisé
Utilisateur utilisateurExistant = utilisateurDao.findById(u.getIdentifiant());
if (utilisateurExistant != null) throw new TodoListException("Identifiant déjà utilisé");
// ok, on peut enregistrer
utilisateurDao.create(u);
}
/**
* Dans cette implémentation, l'association "Utilisateur <-> Tache" est
* initialisée uniquement côté "Tache", ie la propriété "utilisateur".
* Côté "Utilisateur", la collection de tâches n'est pas initialisée.
*/
public List<Tache> lireListeTaches() {
List<Tache> taches = tacheDao.findAll();
for (Tache t : taches) {
Utilisateur u = utilisateurDao.findById(t.getUtilisateur().getIdentifiant());
t.setUtilisateur(u);
}
return taches;
}
/**
* Dans cette méthode, l'association "Utilisateur <-> Tache" est complètement
* initialisée : ie la propriété "utilisateur" côté "Tache" et la propriété
* "taches" coté "Utilisateur".
*/
public Tache lireTache(int id) {
Tache tache = tacheDao.findById(id);
String idUtilisateur = tache.getUtilisateur().getIdentifiant();
Utilisateur u = utilisateurDao.findById(idUtilisateur);
List<Tache> taches = tacheDao.findByUtilisateur(u);
for (Tache t : taches) {
u.ajouterTache(t);
if (t.getId() == id) tache = t;// évite d'avoir deux instances différentes
// pour l'entité recherchée dans le graphe d'objet
}
tache.setUtilisateur(u);
return tache;
}
/**
* Cette méthode consiste à effectuer un INSERT s'il s'agit de sauvegarder
* une nouvelle tâche (reconnue par id == -1). S'il s'agit d'une tâche
* existante (reconnue par id != -1), alors un UPDATE est généré.
*/
public void sauvegarderTache(Tache t) {
if (t.getId() == -1) {
tacheDao.create(t);
}
else {
tacheDao.update(t);
}
}
/**
* Suppression de la tâche passée en paramètre
*/
public void supprimerTache(Tache t) {
tacheDao.delete(t);
}
}
Tests unitaires
De même que pour la couche DAO, il convient de prévoir des tests unitaires afin de vérifier le bon fonctionnement de notre service métier.
La classe de test ci-dessous est similaire à celles que nous avons écrites pour les DAO. Remarquez cependant la méthode de test testEnregistrerUtilisateurExistant
, pour laquelle nous paramétrons l’annotation @Test
avec expected=TodoListException.class
: cela signifie que le test est correct si l’exception TodoListException
est levée par la méthode testée.
package info.jtips.todolist.service;
import info.jtips.todolist.model.Tache;
import info.jtips.todolist.model.Utilisateur;
import info.jtips.todolist.service.impl.TodoListServiceImpl;
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 TodoListServiceTest {
@Test
public void testEnregistrerUtilisateur() {
Utilisateur u = new Utilisateur();
u.setIdentifiant("bb");
u.setPrenom("Bugs");
u.setNom("Bunny");
u.setMotDePasse("mp");
TodoListService service = new TodoListServiceImpl();
service.enregistrerUtilisateur(u);
System.out.println("Terminé");
}
@Test(expected=TodoListException.class)
public void testEnregistrerUtilisateurExistant() {
Utilisateur u = new Utilisateur();
u.setIdentifiant("bb");
u.setPrenom("Bugs");
u.setNom("Bunny");
u.setMotDePasse("mp");
TodoListService service = new TodoListServiceImpl();
service.enregistrerUtilisateur(u);
System.out.println("Terminé");
}
@Test
public void testVerifierLoginOK() {
TodoListService service = new TodoListServiceImpl();
Utilisateur u = service.verifierLogin("bb", "mp");
assertNotNull(u);
assertEquals("Bugs", u.getPrenom());
assertEquals("Bunny", u.getNom());
}
@Test(expected=TodoListException.class)
public void testVerifierLoginKO() {
TodoListService service = new TodoListServiceImpl();
Utilisateur u = service.verifierLogin("bb", "pas le bon mot de passe");
}
@Test
public void testSauvegarderTache1() {// test INSERT
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);
TodoListService service = new TodoListServiceImpl();
service.sauvegarderTache(t1);
service.sauvegarderTache(t2);
}
@Test
public void testSauvegarderTache2() {// test UPDATE
TodoListService service = new TodoListServiceImpl();
Tache t = service.lireTache(2);
t.setLibelle("Tâche modifiée");
service.sauvegarderTache(t);
}
@Test
public void testLireTache() {
TodoListService service = new TodoListServiceImpl();
Tache t = service.lireTache(1);
assertEquals(1, t.getId());
assertEquals("Une première tâche", t.getLibelle());
assertNotNull(t.getUtilisateur());
assertEquals("bb", t.getUtilisateur().getIdentifiant());
assertEquals(2, t.getUtilisateur().getTaches().size());
}
@Test
public void testLireListeTaches() {
TodoListService service = new TodoListServiceImpl();
List<Tache> taches = service.lireListeTaches();
assertEquals(2, taches.size());
}
@Test
public void testSupprimerTaches() {
TodoListService service = new TodoListServiceImpl();
Tache t = service.lireTache(1);
service.supprimerTache(t);
List<Tache> taches = service.lireListeTaches();
assertEquals(1, taches.size());
}
}
Conclusion
Notre couche métier est désormais terminée. Elle peut être utilisée depuis une couche de présentation.
Notez cependant quelques imperfections :
-
Lors d’un appel de méthode sur le service métier, plusieurs connexions JDBC sont ouvertes du fait de l’invocation de plusieurs méthodes DAO (cf méthode
lireTache()
). Cette façon de procéder est coûteuse en ressources machine. -
La gestion transactionnelle n’est pas gérée correctement. En effet, nous nous basons actuellement sur l’auto-commit pour gérér les transactions. Ce mécanisme opère au niveau des DAO, ie plusieurs commit ont lieu lors de l’appel à une méthode du service (cf méthode
enregistrerUtilisateur()
. La démarcation de la transaction devrait plutôt être située au niveau de la classeTodoListServiceImpl
Pour résoudre ces imperfections, plusieurs solutions existent :
-
Gérer l’ouverture / fermeture de la connexion JDBC, ainsi que l’appel aux commit et rollback à l’aide d’un thread local : nous ne détaillerons pas cette approche ici, car elle nécessite l’écriture de code technique, qui peut être très avantageusement remplacé par une des deux solutions ci-dessous
-
Utiliser un conteneur EJB
-
Utiliser Spring Framework
-
Téléchargement du projet
Le projet NetBeans correspondant à la couche métier.
Suite du tutoriel
Bientôt un nouvel article pour la partie présentation avec Swing…