Date de première publication : 2012/06/04
Objectif et contexte
L'objectif de cet exercice est d'ajouter le support de JPA au carnet d'adresse.
- Java SE 8
- Java EE 8
- Netbeans 8.2
- Glassfish 4+
- Mojarra (JSF) 2+
Mise en place de la persistance
Création de la base
Nous avons besoin de préparer le serveur en déclarant une base de données. Dans l'onglet Services, s'assurer que le SGBD est bien démarré (par exemple Java DB).
Avec le clic bouton droit, créer une base de données. Il faut renseigner :
- Database name : carnet
- user : ce que vous voulez
- password : ce que vous voulez mais à confirmer :-)
On voit alors une nouvelle connexion disponible. si l'on clique sur cette nouvelle connexion, il est possible de faire des opérations sur la base : création de tables, insertion de données ...
Entité : lien classe/BDD
Nous allons saupoudrer la classe Personne
pour qu'elle devienne une entité et nous permettre ainsi de faire le lien avec la base de données
@Entity
public class Personne {
La classe Personne doit implémenter l'interface Serializable
.
Il faut également s'occuper de la clé primaire. Deux propositions pour cette clé primaire :
- une clé primaire simple comme le mail
- une clé primaire composite comme par exemple (nom, prenom)
Pour la première proposition, c'est tout simple :
@Id private String mail;
Pour la seconde, c'est un tout petit plus compliqué... Deux écritures JAVA (pour un même résultat SQL) sont envisageables.
Je choisis celui qui a le moins d'impact sur la classe Personne
mais qui est aussi le moins documenté : l'IdClass
@Entity
@IdClass(Composite.class)
public class Personne {
@Id private String nom;
@Id private String prenom;
private String tel;
// ...
}
Il faut maintenant écrire la classe Composite
:
public class Composite implements Serializable {
public String nom;
public String prenom;
public Composite() {
this(null, null);
}
public Composite(String n, String p) {
nom = n;
prenom = p;
}
}
Il faut également redéfinir les méthodes equals()
et hashCode()
. Les attributs nom
et prenom
de la classe Composite
sont bien publics
Le reste ? C'est automatique avec la configuration par exception
Unité de persistence
Il faut maintenant faire le lien entre la base que l'on vient de créer et notre application
Si vous avez commencé à taper du code spécifique (style EntityManager
ou EntityManagerFactory
), l'aide contextuelle va vous proposer de créer une nouvelle unité de persistance.
Sinon, il suffit d'aller dans le menu :
(M) New > (O) Persistence > New Persistence Unit
- Persistence Unit Name : CarnetJPAPU
- Provider : EclipseLink JPA2.1 (Default)
- Data source : New Data Source [1]
- DÉCOCHER Use Java Transaction API
- Generation strategy : create
Sélectionner New Data source, nous envoie sur une nouvelle boîte de dialogue
- JNDI Name : jdbc/__carnet ou jdbc/carnet (ce nom doit être unique)
- New database connection : choisir celle que l'on a faite auparavant
Lorsque l'opération est terminée, un fichier persistence.xml
apparaît dans le
répertoire des fichiers de configuration. Son édition nous montre que, par défaut,
toutes les entités du projet vont être gérées automatiquement par l'unité de persistence.
En situation
Injection
Grâce à l'injection de dépendance, on peut directement utiliser l'unité de persistence que l'on vient de créer.
@PersistenceUnit(unitName="CarnetJPAPU")
EntityManagerFactory emf;
Pour tester, on peut ajouter un élément dans la base de données :
em = emf.createEntityManager();
em.getTransaction().begin();
em.persist(personnes.get(0));
em.getTransaction().commit();
Vous pouvez maintenant ajouter tous les éléments précédents et mettre en commentaire le code : ajouter des éléments qui existent déjà lève une exception
Vous pouvez obtenir plusieurs erreurs
- l'injection ne se fait pas :
emf
est toujoursnull
- L'utilisation des transactions ne vous est pas permise
Résolution des erreurs
Si l'injection de dépendance ne se passe pas comme vous le voulez, il faut tout d'abord vérifier que vous avez autorisé l'injection de dépendance et de contexte lors de la création du projet. Cela peut se voir si un fichier de configuration : bean.xml est présent ou non.
Si ce n'est pas le cas, pas de panique, cela se répare comme cela
(M) File > New (O) Contexts & Dependancy injection > Beans.xml (CDI)
Une autre raison pour laquelle l'injection de dépendances ne se fait pas, c'est que vous cherchez à la faire au "mauvais moment". Ennéffé, elle n'a lieu qu'après la construction du bean managé.
Si vous avez placé votre code dans le constructeur, il faut le déplacer dans une méthode qui sera exécutée après la construction
@PostConstruct
public void methode() {
// et le tour est joué !
}
Si l'utilisation des transactions ne vous est pas permise, c'est que vous avez activé JTA où la gestion des transactions est automatique.
Vérifier la présence d'un élément
La présence d'un élément se fait grâce à ma méthode find()
de l'EntityManager
Personne p = em.find(Personne.class, "un_mail_dans_la_base");
ou
Personne p = em.find(Personne.class, new Composite("yon","loic");
Lire tous les enregistrements de la base
On va permettre la lecture des enregistrements de la base grâce à une requête nommée.
Coté entité :
@Entity
@NamedQuery(name = Personne.FIND_ALL,
query="select p from Personne p")
public class Personne implements Serializable {
// ...
public static final String FIND_ALL = "Personne.findAll";
// ...
}
Coté bean managé :
Query query = em.createNamedQuery(Personne.FIND_ALL);
personnes.addAll(query.getResultList());
Sympa, non ?
Aller plus loin
Je vous propose de créer une nouvelle page Web qui affichera un formulaire pour insérer une nouvelle personne dans la base.
Vous pouvez utiliser les balises panelGrid
(avec l'attribut columns
), outputLabel
, inputText
et buttonCommand
L'action à réaliser du formulaire va être de créer une nouvelle instance de Personne
et de l'insérer dans la base en persistant l'entité.
Cependant, cette opération peut lever une exception, notamment si la personne est déjà présente dans la base. Dans ce cas, je vous propose d'afficher un message d'erreur dans la page de saisie elle-même.
Coté xhtml, il faut placer la zone de message :
<h:messages globalOnly="true" errorClass="error" />
On peut jouer avec les attributs infoXXX
, errorXXX
et warnXXX
en fonction du type de message que l'on veut afficher et layout
si on préfère un tableau aux puces.
Côté Bean, il faut générer le message comme suit :
FacesContext.getCurrentInstance().addMessage(null, new FacesMessage(
FacesMessage.SEVERITY_ERROR , "Personne déjà présente dans la base", null));
Je vous propose d'essayer les règles de navigation :
- retourner à la page de bienvenue si l'opération s'est bien déroulée ("success")
- de rester sur la page d'ajout en cas d'erreur ("failure")
La méthode du bean renvoie failure ou success au lien de renvoyer le nom de la page directement.
Pour faire le lien entre le nom et la page, il suffit d'utiliser l'éditeur graphique disponible sur le fichier de configuration faces-config.xml à créer ou à enrichir.
Il faut tracer un lien entre les pages correspondantes et cliquer sur le nom de la flèche pour préciser le message.
C'est terminé pour cette fois ! On pourrait encore
- vérifier par AJAX la présence de la personne dans la base :
- sécuriser quelques pages de notre application.