I. Création de l'entité▲
Nous allons donc prendre ici une Entité simple représentant une personne : son prénom, son nom, son adresse et son e-mail
Le code a été volontairement réduit pour l'article.
import
java.io.Serializable;
import
java.text.SimpleDateFormat;
import
java.util.ArrayList;
import
java.util.Date;
import
java.util.List;
import
javax.persistence.Entity;
import
javax.persistence.GeneratedValue;
import
javax.persistence.Id;
import
javax.persistence.Table;
import
javax.persistence.UniqueConstraint;
import
org.hibernate.validator.Length;
import
org.hibernate.validator.NotNull;
import
org.jboss.seam.annotations.Name;
@Entity
// on précise que c'est une entité
@Table
(
uniqueConstraints =
@UniqueConstraint
(
columnNames =
"email"
)) // on ajoute une contrainte d'unicité
@NamedQueries
({
// on créer des requêtes nommées (plus rapides, évitent la redondance de code)
@NamedQuery
(
name =
"selectPersonsByEmail"
,
query =
"select person from Person person where person.email = "
+
":email order by person.email"
),
@NamedQuery
(
name =
"selectPersons"
,
query =
"select person from Person person"
)
}
)
public
class
Person implements
Serializable {
/** id */
@Id
@GeneratedValue
private
Long id; // id de l'entité, géré par Hibernate.
/** person firstName */
@NotNull
@Length
(
max =
50
)
private
String firstName;
/** person lastName */
@Length
(
max =
50
)
private
String lastName;
/* person address */
@Length
(
max =
50
)
private
String adress;
/** peson email. */
@Length
(
max =
50
)//Longueur maximale 50 caracteres.
//utilisé pour la validation dans la JSF grâce a s:validate
@NotNull
(
message =
"Vous devez entrer un email."
) // ici on interdit que le champ soit null
@NotEmpty
(
message =
"Vous devez entrer un email."
) // ici on interdit qu'il soit vide.
@Email
(
message =
"e-mail invalide"
)
private
String email;
/// ....Getters/Setters And HashCode And Equals methods...
/// ....
}
Un attribut transient est ajouté à l'entité, c'est juste une petite astuce pour ne pas avoir à trop se compliquer la vie.
C'est utile si on veut par exemple faire une liste d'objets supprimables, vous allez comprendre pourquoi plus tard.
II. Factory, DataModel, DataModelSelection : la liste de données▲
Maintenant, pour gérer une liste de données quoi de mieux qu'un EJB Stateful ?
Bien entendu vous n'êtes pas obligé d'utiliser un EJB, vous pouvez vous contenter d'un Composant Seam simple.
Un composant Seam est moins lourd en termes de mémoire et de performances.
On commence par l'interface, locale dans le cas présent.
@Local
public
interface
SeamList {
/**
* factory for the personList.
*/
void
findPersons
(
);
/**
* select the value.
* */
void
select
(
);
/**
* delete a client.
*/
void
delete
(
);
/**
* remove method.
*/
void
destroy
(
);
}
Et on peut commencer les choses intéressantes : le corps de l'EJB en lui-même.
@Stateful
//
@Scope
(
ScopeType.CONVERSATION)
@Name
(
"seamList"
)
public
class
SeamListBean implements
SeamList {
/** the logger. */
@Logger
private
Log logger;
/** the client list. */
@DataModel
private
List<
Person>
personList;
/** the selected value. */
@DataModelSelection
@Out
(
required =
false
)
private
Person person;
/**
* the entity Manager. On récupère l'entity manager.
*/
@In
(
required =
true
)
private
EntityManager entityManager;
/**
* factory for the personList.
*/
@SuppressWarnings
(
"unchecked"
)
@Factory
(
"personList"
)
public
void
findPersons
(
) {
try
{
Query query =
null
;
query =
entityManager.createNamedQuery
(
"selectPersons"
);
this
.personList =
(
List<
Person>
) query.getResultList
(
);
}
catch
(
NoResultException ex) {
FacesMessages.instance
(
).addToControl
(
"person"
, "Aucune personne trouvee en base."
);
logger.debug
(
"Empty person list"
);
}
}
/**
* select the value.
* */
public
void
select
(
) {
person.setToDelete
(
true
);
}
/**
* delete a client.
*/
public
void
delete
(
) {
personList.remove
(
person);
entityManager.remove
(
person);
person =
null
;
}
/**
* remove method.
*/
@Destroy
@Remove
public
void
destroy
(
) {
//nothing necessary here
}
}
Quelques explications :
- @Stateful précise que c'est un EJB Stateful (Annotation JEE) ;
- @Scope(ScopeType.CONVERSATION) ici préciser le scope conversation est un peu inutile c'est plus une histoire de clarté, Seam stocke par défaut les ejb stateful en conversation. (Annotation Seam) ;
- @Name(« seamList ») déclare l'ejb comme composant Seam pour l'appeler facilement via JSF (Annotation Seam) ;
- @Logger : l'annotation Seam @Logger permet à Seam de choisir le logger du serveur d'application, sur jboss : Log4j ;
- @DataModel : ici on annote la liste @DataModel (Annotation Seam) c'est elle qui va être notre modèle de données. L'annotation en question renvoie à jsf un attribut de type liste à la page jsf sous la forme d'une instance de javax.faces.model.DataModel. Ce qui permet d'utiliser la liste dans une jsf avec une h:dataTable (ou rich:) avec des liens cliquables sur chaque ligne ;
- @DataModelSelection : et là, l'annotation Seam magique, qui va permettre de lier la ligne sélectionnée sur la page web à un objet java. L'annotation @DataModelSelection dit à seam d'injecter l'élément de liste qui correspond à la ligne cliquée ;
- @Out(required = false) : en plus d'être annoté @DataModelSelection, ici on outjecte l'élément pour l'exposer à la valeur sélectionnée directement dans la page.
Donc chaque fois qu'une ligne de la liste cliquable est sélectionnée la personne est injectée dans l'attribut du bean stateful et outjectee dans la variable nommée (contexte événement) personne.
Ce mécanisme est possible grâce au mécanisme de gestion des injections bijectif de Seam ;
- @In(required = true) : on injecte l'entityManager en précisant qu'il est obligatoire. (Annotation Seam) ;
- @Factory(« personList ») : encore une annotation magique de Seam : le @Factory explique à Seam de créer une instance de SeamListBean puis d'invoquer la méthode findPersons pour initialiser l'objet.
C'est la méthode de construction de personnes. Certains y verront un lien avec un design pattern bien connu.
Le @SupressWarnings est uniquement là du fait qu'eclipse lève un Warning du fait du cast obligatoire en List<Person> ;
- @Destroy : dans Seam tous les composants stateful doivent avoir une méthode sans paramètres annotés ainsi.
Il l'utilise ainsi pour retirer les composants Stateful lorsque les contextes se terminent et nettoyer la mémoire.
Vous pouvez donc voir grâce à la méthode delete que grâce au DataModelSelection couplé au DataModel, qu'on peut supprimer très simplement l'élément de la ligne courante.
III. Côté vue : page JSF▲
Et enfin la page JSF pour afficher tout ça :
on utilise une rich:dataTable pour lister les données, un rich:dataScroller pour la pagination
<!DOCTYPE composition PUBLIC
"-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"
>
<
ui
:
composition
xmlns
=
"http://www.w3.org/1999/xhtml"
xmlns
:
s
=
"http://jboss.com/products/seam/taglib"
xmlns
:
ui
=
"http://java.sun.com/jsf/facelets"
xmlns
:
f
=
"http://java.sun.com/jsf/core"
xmlns
:
h
=
"http://java.sun.com/jsf/html"
xmlns
:
rich
=
"http://richfaces.org/rich"
template
=
"/layout/template.xhtml"
>
<
ui
:
define
name
=
"body"
>
<
rich
:
panel
id
=
"personlist"
rendered
=
"#{not empty personList}"
>
<
f
:
facet
name
=
"header"
>
Donnees en base</
f
:
facet>
<
h
:
form>
<
rich
:
spacer
height
=
"15"
/>
<
rich
:
datascroller
for
=
"personList"
boundaryControls
=
"auto"
stepControls
=
"hide"
fastControls
=
"hide"
renderIfSinglePage
=
"false"
/>
<
rich
:
spacer
height
=
"30"
/>
<
rich
:
dataTable
id
=
"personList"
var
=
"person"
value
=
"#{personList}"
style
=
"text-align: center;"
rows
=
"30"
>
<
rich
:
column>
<
f
:
facet
name
=
"header"
>
Email</
f
:
facet>
<
h
:
commandLink
id
=
"personmail"
value
=
"#{person.email}"
action
=
"#{seamList.select}"
/>
</
rich
:
column>
<
rich
:
column>
<
f
:
facet
name
=
"header"
>
Nom</
f
:
facet>
<
h
:
outputText
value
=
"#{person.lastName}"
/>
</
rich
:
column>
<
rich
:
column>
<
f
:
facet
name
=
"header"
>
Prenom</
f
:
facet>
<
h
:
outputText
value
=
"#{person.firstName}"
/>
</
rich
:
column>
<
rich
:
column>
<
f
:
facet
name
=
"header"
>
Action</
f
:
facet>
<
h
:
commandLink
action
=
"#{seamList.delete}"
alt
=
"Supprimer"
>
<
h
:
graphicImage
value
=
"/img/delete.png"
/>
</
h
:
commandLink>
</
rich
:
column>
</
rich
:
dataTable>
</
h
:
form>
</
rich
:
panel>
</
ui
:
define>
</
ui
:
composition>
IV. Explications▲
Ici on a vu donc une liste avec état conservé pour effectuer des actions dessus.
Je l'ai placée en conversation par choix personnel (et les EJB Statefuls sont placés par défaut en conversation par Seam), mais si vraiment votre liste est énorme, vous pouvez la placer en session pour éviter les chargements multiples
Attention cependant aux problèmes de scope courants avec la session… Personnellement je mets le moins d'objets possible en Session pour qu'ils aient un cycle de vie le plus court possible.
Autre conseil, si vous voulez que votre rich:dataTable soit paginée, car vous avez beaucoup de données : vous pouvez utiliser un rich:dataScroller mappé à votre dataTable pour le faire simplement.
Gros inconvénient : la liste entière est chargée au premier chargement de la page, et pas uniquement les données affichées.
Je vous conseille donc de vous développer un système de pagination avec lazy loading assez générique pour vos listes de tailles importantes en utilisant les facilités de l'EjbQL : c'est très simple (setMaxResult, setFirstResult…).
Je rédigerai prochainement un article sur la pagination en lazy loading en utilisant le rich:dataScroller.
V. Unwrap▲
Maintenant si vous voulez une liste systématiquement rechargée (Stateless), il existe une autre façon de faire :
@Name
(
"personList"
)
@Scope
(
ScopeType.STATELESS)
@AutoCreate
public
class
PersonList {
@In
EntityManager entityManager;
@Unwrap
public
List<
Person>
getPersonList
(
) {
return
(
List<
Person>
) entityManager.createQuery
(
"selectPersons"
)
.setHint
(
"org.hibernate.cacheable"
, true
)
.getResultList
(
);
}
}
Ce composant utilise un contexte de persistance géré par Seam.
Contrairement à l'exemple précédent, c'est Seam qui gère le contexte de persistance et pas le conteneur d'ejb.
La liste va ici être rechargée à chaque fois que la page le sera.
L'annotation Unwrap dit à Seam de fournir en type de retour celui de la méthode getPersonList : List<Person>, au lieu du composant PersonList quand on appellera l'objet via #{personList} dans une JSF.
VI. Conclusion▲
Voilà, vous savez maintenant construire rapidement des listes simples à gérer et plutôt performantes.
On a vu à quel point l'annotation @DataModelSelection est utile,
elle permet de directement mapper la ligne sélectionnée dans la page, à un objet de votre liste.