Skip to content

12. [TD] : Implémentation de la couche [DAO] du TD avec [Spring Data]

Mots clés : architecture multicouche, Spring, injection de dépendances, API JPA (Java Persistence API), Spring Data.

Nous allons suivre la même démarche que précédemment pour implémenter la couche [DAO] du TD.

12.1. Support

  • en [1], le dossier [support / chap-12] contient le projet Eclipse de ce chapitre ;

12.2. Le projet Eclipse

Le projet Eclipse sera le suivant :

  
  • [elections.dao.config] : contient la classe de configuration du projet Spring ;
  • [elections.dao.entities] : contient les entités JPA ainsi que la classe d'exception du projet ;
  • [elections.dao.repositories] : contient les interfaces [CrudRepository] pour les tables [CONF] et [LISTES] ;
  • [elections.dao.service] : contient l'implémentation de la couche [DAO]. C'est elle qu'il nous faut écrire ;
  • [elections.dao.console] : contient une classe de test de type [console] ;
  • [pom.xml] : le fichier de configuration du projet Maven ;

Ce projet implémente l'architecture suivante :

La couche [DAO] ne voit que la couche implémentée par [Spring Data].

12.3. Configuration Maven

Travail à faire : construisez le fichier [pom.xml] du projet.

12.4. Les entités de la couche [JPA]

  
  • [ElectionsConfig] est le modèle objet associé à une ligne de la table [CONF] ;
  • [ListeElectorale] est le modèle objet associé à une ligne de la table [LISTES] ;
  • [AbstactEntity] est la classe parent des deux classes précédentes. Elle factorise les champs [id, version] communs aux deux classes ;
  • [ElectionsException] est la classe d'exception du projet ;

12.4.1. La classe [ElectionsException]

  

La classe [ElectionsException] est l'exception non contrôlée décrite au paragraphe 4.3, page 39.

12.4.2. La classe [AbstractEntity]

  

La classe [AbstractEntity] est la suivante :

package elections.dao.entities;

import java.io.Serializable;

import javax.persistence.Column;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.MappedSuperclass;
import javax.persistence.Version;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;

@MappedSuperclass
public abstract class AbstractEntity implements Serializable{
    private static final long serialVersionUID = 1L;

    // propriétés
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "ID")
    protected Long id;
    @Version
    @Column(name = "VERSION")
    protected Long version;

    // constructeurs
    public AbstractEntity() {

    }

    public AbstractEntity(Long id, Long version) {
        this.id = id;
        this.version = version;
    }

    // redéfinition [equals] et [hashcode]
    @Override
    public int hashCode() {
        return (id != null ? id.hashCode() : 0);
    }

    @Override
    public boolean equals(Object entity) {
        if (!(entity instanceof AbstractEntity)) {
            return false;
        }
        String class1 = this.getClass().getName();
        String class2 = entity.getClass().getName();
        if (!class2.equals(class1)) {
            return false;
        }
        AbstractEntity other = (AbstractEntity) entity;
        return id != null && this.id == other.id;
    }

    // signature jSON
    public String toString() {
        ObjectMapper mapper = new ObjectMapper();
        try {
            return mapper.writeValueAsString(this);
        } catch (JsonProcessingException e) {
            e.printStackTrace();
            return null;
        }
    }

    // getters et setters
...
}

C'est la classe décrite au paragraphe 11.3.5.2, page 188, sans les filtres jSON. Ici en effet, les tables [CONF] et [LISTES] ne sont pas liées par une relation de clé étrangère. Or c'est l'existence d'une telle relation avec le mode [lazy loading] qui induit la nécessité de filtres jSON.

12.4.3. La classe [ElectionsConfig]

  

La classe [ElectionsConfig] est l'entité JPA associée à la table [CONF] ;

Travail à faire : construisez la classe [ElectionsConfig].

12.4.4. La classe [ListeElectorale]

  

La classe [ListeElectorale] est l'entité JPA associée à la table [LISTES] ;

Travail à faire : construisez la classe [ListeElectorale].

12.5. La couche [Spring Data]

  

Travail à faire : écrivez les deux interfaces de [Spring Data] pour gérer les deux tables [CONF] et [LISTES] ;

12.6. La couche [DAO]

  

L'interface [IElectionsDao] de la couche [DAO] est la suivante :

package dao.service;

import dao.entities.ElectionsConfig;
import dao.entities.ListeElectorale;

public interface IElectionsDao {

    // configuration de l'élection
    public ElectionsConfig getElectionsConfig();

    // listes candidates
    public ListeElectorale[] getListesElectorales();

    // mise à jour des listes candidates
    public void setListesElectorales(ListeElectorale[] listesElectorales);
}

Travail à faire : écrivez l'implémentation [ElectionsDaoJpa] de l'interface [IElectionsDao].

12.7. Configuration du projet Spring

  

La classe [DaoConfig] configure le projet Spring.

Travail à faire : écrivez la classe [DaoConfig]

12.8. La couche [console]

  

La classe [Main] est la classe exécutable suivante :

package dao.console;

import org.springframework.context.annotation.AnnotationConfigApplicationContext;

import dao.config.AppConfig;
import dao.entities.ElectionsConfig;
import dao.entities.ListeElectorale;
import dao.service.IElectionsDao;

public class Main {

    // source de données
    private static IElectionsDao dao;

    public static void main(String[] args) {
        // récupération du contexte Spring
        AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class);
        // récupération de la source de données
        dao = ctx.getBean(IElectionsDao.class);
        // contenu des deux tables
        ElectionsConfig electionsConfig = dao.getElectionsConfig();
        ListeElectorale[] listes = dao.getListesElectorales();
        // affichage
        System.out.println(String.format("Nombre de sièges à pourvoir : %d", electionsConfig.getNbSiegesAPourvoir()));
        System.out.println(String.format("Seuil électoral : %5.2f", electionsConfig.getSeuilElectoral()));
        System.out.println("Listes candidates----------------");
        for (ListeElectorale liste : listes) {
            System.out.println(liste);
        }
        // fermeture contexte Spring
        ctx.close();
    }

}

La classe [Test01] est un test JUnit :

package dao.tests;

import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.SpringApplicationConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import dao.config.AppConfig;
import dao.entities.ElectionsConfig;
import dao.entities.ListeElectorale;
import dao.service.IElectionsDao;

@SpringApplicationConfiguration(classes = AppConfig.class)
@RunWith(SpringJUnit4ClassRunner.class)
public class Test01 {

    // couche [DAO]
    @Autowired
    private IElectionsDao electionsDao;

    @Before
    public void init() {
        // on nettoie la table [LISTES]
        // listes en compétition
        ListeElectorale[] listes = electionsDao.getListesElectorales();
        // on met à 0 les voix et sièges et elimine à false
        int voix = 0;
        int sièges = 0;
        boolean elimine = false;
        for (ListeElectorale liste : listes) {
            liste.setVoix(voix);
            liste.setSieges(sièges);
            liste.setElimine(elimine);
        }
        // on rend ces données persistantes grâce à la couche [dao]
        electionsDao.setListesElectorales(listes);
    }

    @Test
    public void testElections01() {
        System.out.println("testElections01-------------------------------------");
        // récupération de la configuration des élections
        ElectionsConfig electionsConfig = electionsDao.getElectionsConfig();
        int nbSiegesAPourvoir = electionsConfig.getNbSiegesAPourvoir();
        double seuilElectoral = electionsConfig.getSeuilElectoral();
        Assert.assertEquals(6, nbSiegesAPourvoir);
        Assert.assertEquals(0.05, seuilElectoral, 1E-6);

        // listes en compétition
        ListeElectorale[] listes = electionsDao.getListesElectorales();
        // affichage valeurs lues
        System.out.println("Nombre de sièges à pourvoir : " + nbSiegesAPourvoir);
        System.out.println("Seuil électoral : " + seuilElectoral);
        System.out.println("Listes en compétition ---------------------");
        for (int i = 0; i < listes.length; i++) {
            System.out.println(listes[i]);
        }

        // on affecte des voix et des sièges aux listes
        int voix = 0;
        int sièges = 0;
        boolean elimine = false;
        for (ListeElectorale liste : listes) {
            liste.setVoix(voix);
            liste.setSieges(sièges);
            liste.setElimine(elimine);
            voix += 10;
            sièges += 1;
            elimine = !elimine;
        }

        // on rend ces données persistantes grâce à la couche [dao]
        electionsDao.setListesElectorales(listes);

        // on relit les données
        ListeElectorale[] listesElectorales2 = electionsDao.getListesElectorales();
        // on vérifie les données lues
        Assert.assertEquals(7, listesElectorales2.length);
        voix = 0;
        sièges = 0;
        elimine = false;
        for (ListeElectorale liste : listes) {
            Assert.assertEquals(voix, liste.getVoix());
            Assert.assertEquals(sièges, liste.getSieges());
            Assert.assertEquals(elimine, liste.isElimine());
            voix += 10;
            sièges += 1;
            elimine = !elimine;
        }
        System.out.println("Listes en compétition ---------------------");
        for (int i = 0; i < listes.length; i++) {
            System.out.println(listes[i]);
        }
    }
}

Travail à faire : passez les tests [console] et [JUnit] sur votre couche [DAO].

12.9. Génération de l'archive Maven du projet

En suivant l'exemple du paragraphe 11.3.12, page 205, générez l'archive Maven du projet.