Parmi les possibilités offertes en Java EE pour dialoguer avec une base de données, l’une des stratégies les plus courantes est l’utilisation d’une couche JPA (Java Persistence Api). Celle ci va permettre de disposer d’un modèle ORM (Object Relationnal Mapping) robuste et évolutif qui constituera la brique de base de futures applications ou couches de service.

Dans ce billet, nous allons créer un projet JPA depuis Eclipse afin de générer et manipuler un schéma de données Oracle12c. Ensuite, nous développerons les classes de DAO (Data Access Object) qui exposerons les opérations réalisables en base. Enfin, nous prendrons soin de constituer l’ensemble de tests JUnit permettant de s’assurer du bon fonctionnement de notre brique ORM.
Afin d’imager le propos, l’objectif sera ici de développer les éléments de base d’une application de gestion de résultats de rencontres sportives.
Archi_javaEE_JPA

Détails techniques

Pour profiter au mieux des indications données ici, il est préférable d’utiliser les versions d’outils et d’api listées ci dessous :
– Eclipse Standard 4.3.1 : site d’Eclipse
– Java SE Development Kit 7u45 : site d’Oracle
– Oracle 12c, installé comme décrit dans ce précédent billet
– Glassfish 4, dont nous nous servirons pour disposer d’une implémentation de JPA 2.1 : site de GlassFish
– le plugin WTP 3.5.2 (Web Tools Plateform) pour Eclipse depuis le repository suivant : http://download.eclipse.org/webtools/repository/kepler/

Prérequis Oracle

Pour réaliser les étapes décrites ici, vous aurez besoin d’une instance et d’un utilisateur Oracle12c. Concernant l’installation d’Oracle 12c, vous pouvez vous référer à mon précédent article : Installation Oracle 12c sur windows 8.

A ce stade, vous devez créer un utilisateur qui sera propriétaire du schéma utilisé par JPA. Il est possible pour ce faire d’utiliser la console EM et son menu « sécurité > utilisateurs ». Votre utilisateur doit disposer d’un quota sur le tablespace voulu (ici « USERS », depuis l’onglet « Quotas ») et posséder les mêmes droits que dans la capture d’écran suivante :
G2S_JPA_UserOracle

Configuration Eclipse

Pour commencer, il est nécessaire de « customiser » Eclipse en y ajoutant le plugin WTP, en créant une connexion vers notre base de donnée et en définissant notre environnement serveur GlassFish. Mais avant de démarrer Eclipse, vous allez vous assurer que votre installation utilise bien le jdk 7 que vous aurez préalablement installé. Pour cela, commencez par ajouter au début du fichier « eclipse.ini » le chemin du jdk 7, par exemple :
-vm
C:/Program Files/Java/jdk1.7.0_45/bin

L’installation du plugin dans Eclipse ne devrait pas poser de difficulté particulière : allez dans « Help > Install New Software… » et dans « Work with… » saisissez « The Eclipse Web Tools Platform (WTP) software repository – http://download.eclipse.org/webtools/repository/kepler/ ». Choisissez la version 3.5.2 et le patch correspondant, et installez les.
G2S_JPA_WTP

Grâce au plugin WTP, nous allons maintenant pouvoir créer une connexion vers une base de données. Pour cela, ouvrez la vue « Data Source Explorer » depuis « Window > Show View > Other… », puis cliquez sur « Database Connections > New… ». Dans le premier écran, choisissez « Oracle » et nommez votre connexion, par exemple « Oracle12c_DBConnect ».

Sur le second écran, vous aurez besoin de définir un nouveau driver. Pour cela nous allons nous servir du jar « ojdbc6.jar » situé dans un répertoire « jdbc\lib » de l’installation Oracle 12c. Choisissez « New Driver Definition », sélectionnez le type 11 « Oracle Thin Driver » et depuis l’onglet « JAR List » remplacez le jar présent par celui de votre installation Oracle (que vous pouvez préalablement copier à un autre emplacement comme ici) :
G2S_JPA_DataConnect1
Maintenant que le driver est spécifié, il reste à préciser les paramètres de connexion à la base. Si vous avez suivi l’article précédent pour installer votre base et que vous avez créé un utilisateur « user_test », votre écran devrait ressembler la capture suivante (n’hésitez pas à tester votre connexion) :
G2S_JPA_DataConnect1

Enfin, nous allons installer GlassFish 4 et configurer Eclipse pour permettre l’exploitation des ressources de notre nouveau serveur, et en particulier de son implémentation JPA 2.1. Commencez par télécharger et extraire l’archive du serveur depuis le site GlassFish. N’hésitez pas à consulter les documentations officielles pour en savoir plus sur le serveur GlassFish. Ici, nous n’aurons pas besoin d’aller plus en détail, le reste des opérations s’effectuant depuis Eclipse.

Nous allons définir dans Eclipse un environnement de runtime basé sur notre installation GlassFish. Allez dans le menu « Window > Server > Runtime Environments » et cliquez sur « Add ». L’adaptateur Eclipse pour GlassFish n’est pas installé par défaut, donc cliquez sur « Download additional server adapters » et sélectionnez « GlassFish Tools » comme sur la capture ci contre :
G2S_JPA_WTP
De retour à l’écran de configuration du runtime, sélectionnez le jdk 7, un nom pour cette configuration et enfin l’emplacement de votre installation GlassFish :
G2S_JPA_WTP

Création du projet JPA dans Eclipse

Nous disposons désormais d’un environnement complet et opérationnel, nous allons donc pouvoir créer notre projet dans Eclipse et enfin commencer à manipuler un peu de code.

Comme il va s’agir de la brique modèle JPA d’une ébauche de gestionnaire de scores de rencontres sportives, nous allons nommer le projet G2S_Model. Depuis la vue « Project Explorer », faites un clic droit puis « New > Project… » et sélectionnez « JPA Project », puis remplissez les champs du formulaire comme sur l’écran suivant :
G2S_JPA_1
Passez ensuite l’écran spécifique Java, pour accéder à celui de gestion de la configuration JPA. Ici, nous allons spécifier la façon dont seront gérées les communications entre la base de données et la couche JPA de notre projet Eclipse. Nous allons utiliser « EclipseLink 2.5x », en précisant que l’implémentation JPA à utiliser est celle de l’installation GlassFish, que la connexion vers la base de données est définie dans « Oracle12c_DBConnect, et sans oublier d’ajouter le driver Oracle. Le résultat est le suivant :
G2S_JPA_2

Malgré que nous ayons déjà indiqué à EclipseLink comment utiliser les ressources GlassFish, nous allons aussi avoir besoin d’accéder à ces bibliothèques Java EE depuis nos classes et en particulier à l’implémentation JPA 2.1. Pour en terminer avec la configuration de notre projet dans Eclipse, nous allons donc immédiatement compléter le classpath en y incluant les bibliothèques de GlassFish et celles pour JUnit.

Faites un clic droit sur le projet et ouvrez « Properties ». Un bug Eclipse empêche d’ajouter cette référence d’une façon classique via « Java Build Path », mais il est possible d’obtenir le résultat souhaité en passant par « Project Facets » et en sélectionnant « Utility Module ».
G2S_JPA_1
L’ajout de JUnit est plus simple : sélectionner « Java Build Path » et depuis l’onglet « Libraries » cliquez sur « Add Library… ». Sélectionnez « JUnit », cliquez sur « Next » et choisissez la version de JUnit souhaité, ici nous utiliserons JUnit 4.
G2S_JPA_1
A ce stade, le projet dans Eclipse présente la structure suivante :
G2S_JPA_1

Modèle JPA

Pour aller au plus efficace, je propose ici de copier directement les classes depuis les sources mises à disposition, mais je vous invite à manipuler, à tester et à vous documenter si vous souhaitez compléter votre compréhension du fonctionnement JPA.
Pour le moment nous n’avons aucune table dans notre schéma Oracle, mais Eclipse nous fournit des outils qui vont permettre de modéliser notre besoin et de générer simultanément la structure en base et la couche d’entités qui la représente. Commencez par créer un package « g2s.model » dans le répertoire « src », puis copiez y les classes « Participant.java », « Resultat.java » et « Rencontre.java ».

Nous allons maintenant ouvrir une première fois le diagramme JPA en faisant un clic droit sur « JPA Content » dans l’explorateur de projets, et en sélectionnant « Open Diagram ». De cette façon, Eclipse complète le projet en faisant apparaitre un répertoire « Diagrams » avec un fichier XML au nom du projet, ici « G2S_Model.xml », qui définit les positions des différents éléments du schéma. Modifier le contenu du fichier par celui des sources, fermez puis ouvrez à nouveau le diagramme et vous devriez obtenir la structure suivante :
G2S_JPA_1

Dans un souci de simplification, le schéma proposé ne permet que d’enregistrer le nom des participants, les lieux et dates des rencontres et enfin des résultats pour chaque participant à une rencontre. De cette façon, il est par exemple possible de gérer les scores d’une compétition de tennis, de rugby ou même de golf. Ce schéma simple pourra être enrichi de nombreuses façons, ajout de détails pour les participants ou les rencontres par exemple, ou regroupement de rencontres en tournois et championnats, et la combinaison Eclipse-JPA nous apporte l’outillage permettant de toujours maintenir le schéma JPA en accord avec la structure de la base de donnée.
Nous avions constaté ne pas disposer de tables dans notre schéma Oracle, mais maintenant que nous disposons d’un diagramme JPA satisfaisant, nous allons pouvoir y remédier. Nous allons commencer par adapter notre fichier « persistence.xml » en y injectant le contenu suivant qui spécifie la stratégie transactionnelle, les entités à persister et les paramètres JDBC de connexion vers la base cible :

<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.1"
	xmlns="http://xmlns.jcp.org/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence http://xmlns.jcp.org/xml/ns/persistence/persistence_2_1.xsd">
	<persistence-unit name="G2S_JPA" transaction-type="RESOURCE_LOCAL">
		<provider>org.eclipse.persistence.jpa.PersistenceProvider</provider>
		<class>g2s.model.Participant</class>
		<class>g2s.model.Rencontre</class>
		<class>g2s.model.Resultat</class>
		<exclude-unlisted-classes>false</exclude-unlisted-classes>
		<properties>
			<property name="javax.persistence.jdbc.driver" value="oracle.jdbc.OracleDriver" />
			<property name="javax.persistence.jdbc.url" value="jdbc:oracle:thin:@localhost:1521:orcl12c" />
			<property name="javax.persistence.jdbc.user" value="user_test" />
			<property name="javax.persistence.jdbc.password" value="user_test" />
		</properties>
	</persistence-unit>
</persistence>

Maintenant nous allons faire générer nos tables, et les scripts DDL qui correspondent, par Eclipse. Faites un clic droit sur le projet, allez dans « JPA Tools > Generate Tables from Entities… ». Si tout s’est bien passé, deux fichiers SQL sont apparus : « createDDL.sql » et « dropDDL.sql ». Vous pouvez aussi utiliser la connexion depuis la vue « Data Source Explorer » pour parcourir le schéma Oracle et vérifier la création des tables. A noter enfin, que la démarche choisit ici est de modéliser la base dans Eclipse pour la générer par la suite dans Oracle mais il aurait été tout aussi envisageable de générer les entités en accord avec une structure de données existante.
Nos entités sont prêtes à être utilisées. On notera en passant certaines annotations spécifiques :

@Table(name = "PARTICIPANT", uniqueConstraints = @UniqueConstraint(name = "UNQ_PART_NOM", columnNames = "PART_NOM"))
Cette structure permet de définir une contrainte d’unicité sur le champ « PART_NOM » de la table « PARTICULIER », ainsi deux participants ne pourront pas avoir le même nom en base et la tentative de création d’un participant avec un nom déjà utilisé provoquera la levée d’une exception.

@NamedQuery(name = "participantParNom", query = "select part from PARTICIPANT part where part.part_nom = :participant")
Il s’agit d’une « NamedQuery », c’est à dire une requête particulière identifiée par un nom, ici celle-ci attendant un paramètre « participant » lors de l’appel. Nous verrons plus loin comment l’utiliser depuis un DAO.

@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "SEQGENPART")
@SequenceGenerator(name = "SEQGENPART", sequenceName = "SEQPART", initialValue = 1, allocationSize = 1)

Ici, il faut comprendre l’enchainement des trois annotations : la première déclare l’attribut comme étant l’identifiant pour la table, le second définit la stratégie d’incrémentation à appliquer, ici l’usage d’une séquence, et enfin le troisième déclare la séquence en question avec ses spécificités.

Surcouche DAO

Dans l’absolu, il serait possible de laisser le métier exploiter directement les entités de persistance JPA, et l’usage de DAO sur JPA ne fait d’ailleurs pas toujours l’unanimité. Pour ma part, il me semble plus propre d’utiliser un DAO pour éviter la manipulation directe des entités depuis le métier et ainsi décorréler la couche modèle de la couche de service.
Ici, nous allons concevoir un DAO générique qui va assurer les opérations élémentaires CRUD (Create, Read, update, Delete) et permettre aussi l’exécution de requêtes spécifiques (« NamedQueries »). Créez cette classe dans un nouveau package « g2s.dao » :

package g2s.dao;

import java.io.Serializable;
import java.lang.reflect.ParameterizedType;
import java.util.Collection;
import java.util.Map;

import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.Query;

public abstract class G2sDAO<Ent, Identity extends Serializable> {

	@PersistenceContext
	private EntityManager eManager;

	private Class<Ent> entityClass;

	@SuppressWarnings("unchecked")
	public G2sDAO() {
		this.entityClass = (Class<Ent>) ((ParameterizedType) getClass()
				.getGenericSuperclass()).getActualTypeArguments()[0];
	}

	public void setEntityManager(EntityManager pEm) {
		this.eManager = pEm;
	}

	public EntityManager getEntityManager() {
		return eManager;
	}

	public Ent lire(Identity pId) {
		return eManager.find(entityClass, pId);
	}

	public void creer(Ent entity) {
		eManager.persist(entity);
	}

	public void editer(Ent entity) {
		eManager.merge(entity);
	}

	public void supprimer(Ent entity) {
		eManager.remove(entity);
	}

	@SuppressWarnings("unchecked")
	public Collection<Ent> requeteSpeciale(String nomRequete,
			Map<String, Object> parametres) {
		Query query = eManager.createNamedQuery(nomRequete);
		for (String param : parametres.keySet()) {
			query.setParameter(param, parametres.get(param));
		}
		return query.getResultList();
	}

}

Nous allons pouvoir maintenant compléter cette couche de DAO en générant une classe héritant de notre DAO générique pour chaque entité. Ces classes pourront rester vides lorsque l’entité ne nécessite que des manipulations élémentaires, mais elles permettront surtout d’ajouter les requêtes spécifiques, comme ici « participantParNom(String nomParticipant) » qui permet de retrouver un participant par son nom (celui-ci étant unique en base du fait de la contrainte « UNQ_PART_NOM »).

package g2s.dao;

import g2s.model.Participant;

import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class ParticipantDAO extends G2sDAO<Participant, Long> {

	public Participant participantParNom(String nomParticipant) {
		// Execution de requete speciale
		Map<String, Object> params = new HashMap<String, Object>();
		params.put("participant", nomParticipant);
		Collection<Participant> resultats = super.requeteSpeciale("participantParNom", params);
		return resultats.isEmpty()? null : (Participant)resultats.toArray()[0];
	}
	
	public List<Participant> trouverTousLesParticipants() {
		// Execution de requete speciale
		Collection<Participant> resultats = super.requeteSpeciale("trouverTousLesParticipants", new HashMap<String, Object>());
		return resultats.isEmpty()? null : (List<Participant>)resultats;
	}

}

Tests JUnit

En théorie, notre couche JPA est opérationnelle et peut être exploitée depuis sa surcouche de DAO, mais il s’agit maintenant de s’en assurer. Pour cela nous allons compléter notre projet par un ensemble de tests JUnit. Ces tests sont utiles maintenant mais ils le seront aussi à chaque fois qu’il s’agira de faire évoluer le modèle et le projet.
De la même façon que nous avions monté un DAO générique, nous allons ici nous appuyer sur un « DAOTest » générique qui va gérer les aspects communs aux différentes classes de test et en particulier la gestion des transactions. Sur ce point, il peut être tentant d’implémenter le mécanisme transactionnel au niveau des DAO, mais en pratique cela crée souvent des problématiques de commit intermédiaires difficiles à identifier et à éliminer en cas d’erreur. Ici les classes de test gèrent donc les transactions comme le feront par la suite les classes appelantes des DAO.
Commencez par créer un package « g2s.dao.tests » et créez y la classe suivante :

package g2s.dao.tests;

import static org.junit.Assert.assertNull;
import g2s.dao.G2sDAO;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;

public abstract class G2sDAOTest<Ent, Identity extends Serializable> {

	protected static final String MSG_TEST_CREATION = "test creation";
	protected static final String MSG_TEST_EDITION = "test edition";
	protected static final String MSG_TEST_SUPPRESSION = "test suppression";
	protected static final String MSG_TEST_SPECIAL = "test special";
	protected static final EntityManagerFactory entManagerFact = Persistence
			.createEntityManagerFactory("G2S_JPA");

	@SuppressWarnings("rawtypes")
	public void startConnection(List<G2sDAO> daos) {
		EntityManager entManager = entManagerFact.createEntityManager();
		for (G2sDAO dao : daos) {
			dao.setEntityManager(entManager);
		}
		entManager.getTransaction().begin();
	}

	@SuppressWarnings("rawtypes")
	public void startConnection(G2sDAO dao) {
		List<G2sDAO> daos = new ArrayList<>();
		daos.add(dao);
		startConnection(daos);
	}

	public void closeConnection(G2sDAO<Ent, Identity> dao) {
		dao.getEntityManager().getTransaction().commit();
		dao.getEntityManager().close();
	}

	public void testSuppression(G2sDAO<Ent, Identity> dao, Identity pId) {
		// debut du test
		startConnection(dao);

		// Lecture et suppression des donnees pour test
		Ent ent = dao.lire(pId);
		dao.supprimer(ent);

		// Lecture des donnees supprimees pour verification
		Ent entSup = dao.lire(pId);
		assertNull(G2sDAOTest.MSG_TEST_SUPPRESSION, entSup);

		// fin du test
		closeConnection(dao);
	}

}

Nous allons maintenant créer une classe de test pour chaque DAO de façon à tester les opérations CRUD. Il est possible d’y ajouter aussi les tests pour les requêtes spécifiques mais vous pouvez préférer, pour une question de maintenabilité, créer une ou plusieurs classes spéciales contenant ces tests plus spécifiques. Ici nous compléterons d’ailleurs nos tests par un cas plus pratique portant sur les résultats du dernier tournoi des six nations. La classe de test pour le DAO « Participant » aura donc la strcture suivante :

package g2s.dao.tests;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import g2s.dao.ParticipantDAO;
import g2s.model.Participant;

import org.junit.FixMethodOrder;
import org.junit.Test;
import org.junit.runners.MethodSorters;

@FixMethodOrder(MethodSorters.NAME_ASCENDING)
public class ParticipantDAOTest extends G2sDAOTest<Participant, Long> {

	protected static final Long ID_PARTICPANT = 1000l;
	protected static final String NOM_PARTICPANT = "nom_part";
	protected static final String NOM_PARTICPANT_MAJ = "nom_part_maj";
	private static ParticipantDAO dao = new ParticipantDAO();

	/**
	 * Creation participant en base, puis lecture et verification des valeurs
	 */
	@Test
	public void testCreation() {
		// debut du test
		startConnection(dao);

		// Injection des donnees de test
		Participant participantTest = new Participant();
		participantTest.setPart_id(ID_PARTICPANT);
		participantTest.setPart_nom(NOM_PARTICPANT);
		dao.creer(participantTest);

		// Lecture des donnees inserees pour verification
		Participant participantLu = dao.lire(ID_PARTICPANT);
		assertNotNull(G2sDAOTest.MSG_TEST_CREATION, participantLu);
		assertEquals(G2sDAOTest.MSG_TEST_CREATION, ID_PARTICPANT.longValue(),
				participantLu.getPart_id());
		assertEquals(G2sDAOTest.MSG_TEST_CREATION, NOM_PARTICPANT,
				participantLu.getPart_nom());

		// fin du test
		closeConnection(dao);
	}

	/**
	 * Lecture particpant en base, modification des valeurs et relecture pour
	 * verification
	 */
	@Test
	public void testEdition() {
		// debut du test
		startConnection(dao);

		// Lecture et MAJ donnees lu pour test edition
		Participant participantLu = dao.lire(ID_PARTICPANT);
		participantLu.setPart_nom(NOM_PARTICPANT_MAJ);
		dao.editer(participantLu);

		// Lecture des donnees inserees pour verification
		Participant participantMaj = dao.lire(ID_PARTICPANT);
		assertNotNull(G2sDAOTest.MSG_TEST_EDITION, participantMaj);
		assertEquals(G2sDAOTest.MSG_TEST_EDITION, ID_PARTICPANT.longValue(),
				participantMaj.getPart_id());
		assertEquals(G2sDAOTest.MSG_TEST_EDITION, NOM_PARTICPANT_MAJ,
				participantMaj.getPart_nom());

		// fin du test
		closeConnection(dao);
	}

	/**
	 * Test de suppression des donnees
	 */
	@Test
	public void testSuppression() {
		super.testSuppression(dao, ID_PARTICPANT);
	}

	/**
	 * Test de la contrainte d'unicite de la table
	 */
	@Test
	public void testContrainteUnicite() {
		// premiere injection
		startConnection(dao);
		Participant participantTest = new Participant();
		participantTest.setPart_id(ID_PARTICPANT);
		participantTest.setPart_nom(NOM_PARTICPANT);
		dao.creer(participantTest);
		closeConnection(dao);

		// seconde injection -> provoque erreur
		startConnection(dao);
		Participant participantTestUnqC = new Participant();
		participantTestUnqC.setPart_nom(NOM_PARTICPANT);
		dao.creer(participantTestUnqC);
		try {
			closeConnection(dao);
		} catch (Exception exc) {
			System.out.println("Exception levee comme attendue");
		}

		// suppression des donnees test
		this.testSuppression();
	}

}

Et notre tournoi des six nations :

package g2s.dao.tests;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import g2s.dao.ParticipantDAO;
import g2s.dao.RencontreDAO;
import g2s.dao.ResultatDAO;
import g2s.dao.G2sDAO;
import g2s.model.Participant;
import g2s.model.Rencontre;
import g2s.model.Resultat;

import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import org.junit.After;
import org.junit.Before;
import org.junit.FixMethodOrder;
import org.junit.Test;
import org.junit.runners.MethodSorters;

@FixMethodOrder(MethodSorters.NAME_ASCENDING)
@SuppressWarnings("rawtypes")
public class TournoiSixNationsDAOTest extends G2sDAOTest {

	// injection des dao
	private static ParticipantDAO particpantDAO = new ParticipantDAO();
	private static RencontreDAO rencontreDAO = new RencontreDAO();
	private static ResultatDAO resultatDAO = new ResultatDAO();

	// les particpants
	public static final List<String> PARTICIPANTS = Arrays.asList(
			"Angleterre", "France", "Ecosse", "Irlande", "Italie", "Galles");

	// les rencontres
	public static final Map<String, Timestamp> RENCONTRES = new HashMap<String, Timestamp>();
	static {
		RENCONTRES.put("RENC1_Cardiff", new Timestamp(new GregorianCalendar(
				2014, 1, 1).getTimeInMillis()));
		RENCONTRES.put("RENC2_Paris", new Timestamp(new GregorianCalendar(2014,
				1, 1).getTimeInMillis()));
		RENCONTRES.put("RENC3_Dublin", new Timestamp(new GregorianCalendar(
				2014, 1, 2).getTimeInMillis()));
		RENCONTRES.put("RENC4_Dublin", new Timestamp(new GregorianCalendar(
				2014, 1, 8).getTimeInMillis()));
		RENCONTRES.put("RENC5_Edimbourg", new Timestamp(new GregorianCalendar(
				2014, 1, 8).getTimeInMillis()));
		RENCONTRES.put("RENC6_Paris", new Timestamp(new GregorianCalendar(2014,
				1, 9).getTimeInMillis()));
		RENCONTRES.put("RENC7_Cardiff", new Timestamp(new GregorianCalendar(
				2014, 1, 21).getTimeInMillis()));
		RENCONTRES.put("RENC8_Rome", new Timestamp(new GregorianCalendar(2014,
				1, 22).getTimeInMillis()));
		RENCONTRES.put("RENC9_Londres", new Timestamp(new GregorianCalendar(
				2014, 1, 22).getTimeInMillis()));
		RENCONTRES.put("RENC10_Dublin", new Timestamp(new GregorianCalendar(
				2014, 2, 8).getTimeInMillis()));
		RENCONTRES.put("RENC11_Edimbourg", new Timestamp(new GregorianCalendar(
				2014, 2, 8).getTimeInMillis()));
		RENCONTRES.put("RENC12_Londres", new Timestamp(new GregorianCalendar(
				2014, 2, 9).getTimeInMillis()));
		RENCONTRES.put("RENC13_Rome", new Timestamp(new GregorianCalendar(2014,
				2, 15).getTimeInMillis()));
		RENCONTRES.put("RENC14_Cardiff", new Timestamp(new GregorianCalendar(
				2014, 2, 15).getTimeInMillis()));
		RENCONTRES.put("RENC15_Paris", new Timestamp(new GregorianCalendar(
				2014, 2, 15).getTimeInMillis()));
	}

	// les resultats
	public static final Map<String, Integer> RESULTATS = new HashMap<String, Integer>();
	static {
		RESULTATS.put("RENC1_Galles", 23);
		RESULTATS.put("RENC1_Italie", 15);
		RESULTATS.put("RENC2_France", 26);
		RESULTATS.put("RENC2_Angleterre", 24);
		RESULTATS.put("RENC3_Irlande", 28);
		RESULTATS.put("RENC3_Ecosse", 6);
		RESULTATS.put("RENC4_Irlande", 26);
		RESULTATS.put("RENC4_Galles", 3);
		RESULTATS.put("RENC5_Ecosse", 0);
		RESULTATS.put("RENC5_Angleterre", 20);
		RESULTATS.put("RENC6_France", 30);
		RESULTATS.put("RENC6_Italie", 10);
		RESULTATS.put("RENC7_Galles", 27);
		RESULTATS.put("RENC7_France", 6);
		RESULTATS.put("RENC8_Italie", 20);
		RESULTATS.put("RENC8_Ecosse", 21);
		RESULTATS.put("RENC9_Angleterre", 13);
		RESULTATS.put("RENC9_Irlande", 10);
		RESULTATS.put("RENC10_Irlande", 46);
		RESULTATS.put("RENC10_Italie", 7);
		RESULTATS.put("RENC11_Ecosse", 17);
		RESULTATS.put("RENC11_France", 19);
		RESULTATS.put("RENC12_Angleterre", 29);
		RESULTATS.put("RENC12_Galles", 18);
		RESULTATS.put("RENC13_Italie", 11);
		RESULTATS.put("RENC13_Angleterre", 52);
		RESULTATS.put("RENC14_Galles", 51);
		RESULTATS.put("RENC14_Ecosse", 3);
		RESULTATS.put("RENC15_France", 20);
		RESULTATS.put("RENC15_Irlande", 22);
	}

	@SuppressWarnings("unchecked")
	@Before
	public void initTest() {
		System.out.println("before");
		List<G2sDAO> daos = new ArrayList<>();
		daos.add(particpantDAO);
		daos.add(rencontreDAO);
		daos.add(resultatDAO);
		new TournoiSixNationsDAOTest().startConnection(daos);
	}

	@SuppressWarnings("unchecked")
	@After
	public void cleanTest() {
		System.out.println("after");
		new TournoiSixNationsDAOTest().closeConnection(particpantDAO);
	}

	/**
	 * Creation resultat en base, puis lecture et verification des valeurs
	 * 
	 * @throws IllegalAccessException
	 * @throws IllegalArgumentException
	 */
	@Test
	public void testCreation() throws IllegalArgumentException,
			IllegalAccessException {
		System.out.println("testCreation");
		// Injection des donnees de test
		for (String participant : PARTICIPANTS) {
			creerParticipant(participant);
		}
		for (String keyRencontre : RENCONTRES.keySet()) {
			creerRencontre(keyRencontre.split("_")[1],
					RENCONTRES.get(keyRencontre));
			for (String keyResultat : RESULTATS.keySet()) {
				if (keyResultat.split("_")[0].equalsIgnoreCase((keyRencontre
						.split("_")[0]))) {
					creerResultat(keyResultat.split("_")[1],
							keyRencontre.split("_")[1],
							RENCONTRES.get(keyRencontre),
							RESULTATS.get(keyResultat));
				}
			}
		}
	}

	@Test
	public void testFrance() throws IllegalArgumentException,
			IllegalAccessException {
		// test bon enregistrement du participant France
		Participant france = particpantDAO.participantParNom("France");
		assertNotNull(G2sDAOTest.MSG_TEST_CREATION, france);
		
		// test des resultats du participant France
		Collection<Resultat> listResultats = resultatDAO.tousLesResultatsParParticipant(france);
		assertEquals(G2sDAOTest.MSG_TEST_CREATION, 5, listResultats.size());
		
		int compteurMatchParis = 0;
		Iterator<Resultat> iter = listResultats.iterator();
		while(iter.hasNext()) {
			Resultat res = iter.next();
			if ("Paris".equalsIgnoreCase(res.getRencontre().getRenc_lieu())) {
				compteurMatchParis++;
			}
		}
		assertEquals(G2sDAOTest.MSG_TEST_CREATION, 3, compteurMatchParis);
	}

	private void creerParticipant(String nomParticipant) {
		Participant participant = new Participant();
		participant.setPart_nom(nomParticipant);
		particpantDAO.creer(participant);
	}

	private void creerRencontre(String lieuRencontre, Timestamp dateRencontre) {
		Rencontre rencontre = new Rencontre();
		rencontre.setRenc_lieu(lieuRencontre);
		rencontre.setRenc_date(dateRencontre);
		rencontreDAO.creer(rencontre);
	}

	private void creerResultat(String nomParticipant, String lieuRencontre,
			Timestamp dateRencontre, Integer score) {
		Participant part = particpantDAO.participantParNom(nomParticipant);
		Rencontre renc = rencontreDAO.rencontreParLieuEtDate(lieuRencontre,
				dateRencontre);
		Resultat resultat = new Resultat();
		resultat.setRes_score(score);
		resultat.setParticipant(part);
		resultat.setRencontre(renc);
		resultatDAO.creer(resultat);
	}

}

Les annotations spécifiques ici sont simplement
– @Test : déclare la méthode comme étant un cas de test JUnit.
– @FixMethodOrder(MethodSorters.NAME_ASCENDING) : indique à JUnit d’exécuter les cas de test dans un ordre alphabétique.
– @Before et @After : indique à JUnit que ces méthodes doivent systématiquement être exécuter avant et après chaque test.

Nos tests sont prêts à être utilisés. Pour les lancer, vous pouvez choisir d’exécuter l’ensemble en faisant un clic droit sur le package « tests » puis « Run as > JUnit test ». Mais vous pouvez aussi choisir de n’exécuter qu’une seule classe ou même un seul test, toujours avec clic droit et « Run as > JUnit test ».
structure_G2S

Dans l’idéal, chaque test devrait être rendu autonome par injection préalable des données nécessaires mais c’est évidemment plus compliqué lorsque les tests portent sur les opérations CRUD. Les tests proposés ici ne sont pas suffisamment indépendants les uns des autres, par exemple il n’est possible de réaliser un test de suppression qu’après avoir exécuté celui de création, mais c’est surtout les tests portant sur les requêtes spécifiques qui auront un intérêt par la suite et nous disposons donc d’une bonne base pour les mettre en œuvre quand cela sera nécessaire.
A noter que vous pouvez facilement réinitialiser la base de données par un clic droit sur le projet puis « JPA Tools > Generate Tables from Entities… » comme vu précédemment. Cette situation est idéale en phase de développement, mais par la suite il faudra évidemment changer cette stratégie de façon à faire évoluer le modèle sans perdre systématiquement les données.

Conclusion

Nous disposons désormais d’un projet JPA dans Eclipse avec des tests JUnit adaptés. Cet outillage permettra par la suite de faire évoluer le modèle de données tout en conservant à jour la couche de persistance associée. Mais cette première brique est surtout destinée à être utilisée dans une architecture plus complexe et nous verrons dans un futur billet comment construire une couche de services permettant d’exposer ce modèle.

Laisser un commentaire

Aucun commentaire pour le moment

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *


*

Vous pouvez utiliser ces balises et attributs HTML : <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>