Professional Documents
Culture Documents
plan
Interrogation dune entit
Le gestionnaire dentits
Obtenir un gestionnaire dentits
Contexte de persistance
Manipulation des entits
LAPI de cache
JPQL
Select, From, Where, Oreder By, Group By et Having, Suppressions
multiples et Mises jour multiples.
Interrogation des entits
Mthodes de rappel et couteurs
Requtes
Requtes dynamiques, Requtes nommes, Requtes natives, Requtes
pour procdures stockes
Concurrence
Gestion de version, Verrouillage optimiste, Verrouillage pessimiste
Cycle de vie dentit
Mthodes de rappel
1
Gestion des objets persistants
JPA a deux aspects.
Associer des objets une base de donnes relationnelle.
Interrogation de ces objets une fois quils ont t associs une base.
Le gestionnaire dentits, lment central de JPA, permet de crer, rechercher,
supprimer et synchroniser les objets avec la base et permet dexcuter
diffrentes sortes de requtes JPQL sur les entits, comme des requtes
dynamiques, statiques ou natives.
Le gestionnaire dentits autorise galement la mise en place de mcanismes
de verrouillage sur les donnes.
Pour le monde base de donnes relationnelles repose sur SQL qui doit tre
dform pour convenir un langage objets (java). Cest l que JPQL (Java
Persistence Query Language) entre en jeu.
JPQL est le langage quutilise JPA pour interroger les entits stockes dans une
base de donnes relationnelle.
JPQL manipule des objets et des attributs et ne voit pas la structure de la base.
2
Interrogation dune entit
On utilise une classe Main qui utilise linterface EntityManager pour stocker une instance
de Book dans la table.
public class Main {
public static void main(String[] args) {
// Creates an instance of book
Book book = new Book(); book.setId(1234L);
book.setTitle("The Hitchhiker's Guide to the Galaxy"); book.setPrice(12.5F);
book.setDescription("Science fiction comedy series created by Douglas Adams.");
book.setIsbn("1-84023-742-2"); book.setNbOfPage(354); book.setIllustrations(false);
// Gets an entity manager and a transaction
EntityManagerFactory emf =
Persistence.createEntityManagerFactory(Constants.PERSISTENCE_UNIT_NAME);
EntityManager em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction();
// Persists the book to the database
tx.begin(); em.persist(book); tx.commit();
// Retreives the book by its identifier
book = em.find(Book.class, 1234L);
System.out.println(book);
em.close(); emf.close();
} 2014/2015
} Java Entreprise Edition : JEE 7 5
3
Interrogation dune entit
On na pas utilis des requtes JPQL ni dappels JDBC. La classe main interagit
avec la base de donnes via linterface EntityManager, qui fournit un ensemble
de mthodes qui permettent de raliser des oprations sur lentit Book.
LentityManager utilise le fournisseur de persistance pour interagir avec la base.
Lorsquon appelle lune des mthodes de lEntityManager, le fournisseur produit
et excute une instruction SQL via JDBC correspondant.
4
Le gestionnaire dentit
Le gestionnaire dentits est une composante essentielle en JPA.
JPA gre ltat et le cycle de vie des entits et les interroge dans un contexte de
persistance.
Cest galement lui responsable de la cration et de la suppression des
instances dentits persistantes et qui les retrouve partir de leur cl primaire.
Il peut les verrouiller pour les protger des accs concurrents en utilisant un
verrouillage optimiste ou pessimiste.
Lorsquun gestionnaire dentits se rfre une entit, celle-ci est dite gre.
Avant cela, lentit est considre comme un POJO classique (elle est
dtache).
Avec JPA, lorsquune entit est gre, il devient possible deffectuer des
oprations de persistance (synchroniser ltat de lentit avec la base).
Lorsquune entit est dtache (non gre), elle devient un simple POJO et
peut tre utilise par les autres couches sans que son tat soit synchronis
Le gestionnaire dentit
5
Le gestionnaire dentit
6
Obtenir un gestionnaire dentits
Dans un environnement gr par un conteneur, cest ce dernier qui gre les
transactions (pas besoin dappeler begin(), commit(), etc.).
Dans un environnement gr par lapplication, cest cette dernire qui est
responsable de lobtention explicite de lEntityManager et de la gestion de son
cycle de vie (voir le code prcdent).
Dans un environnement gr par conteneur, lapplication sexcute dans une
servlet ou dans un conteneur dEJB.
En Java EE, pour obtenir un gestionnaire dentits consiste soit utiliser
lannotation @PersistenceContext pour en injecter un, soit utiliser JNDI.
Un composant qui sexcute dans un conteneur (servlet, EJB, service web, )
na pas besoin de crer ou de fermer le gestionnaire dentits puisque son cycle
de vie est gr par le conteneur.
Exemple dune session sans tat dans laquelle on injecte une rfrence lunit
de persistance chapter04PU
7
Obtenir un gestionnaire dentits
Le code prcdent est bien plus simple:
Les beans sans tat grent les transactions, on na pas besoin dappeler
commit() ou rollback().
La plupart des entits utilises ici nimplmentent pas linterface
Serializable car elles nen ont pas besoin pour tre persistantes.
Si on a des appels distants, conteneur EJB externe, les entits doivent
implmentes linterface Serializable pour indiquer au compilateur quil faut
que tous les champs de lentit soient srialisables afin quune instance
puisse tre srialise dans un flux doctets et passe par RMI (Remote
Method Invocation).
Contexte de persistance
Le contexte de persistance est lensemble des instances dentits gres un
instant donn.
Dans un contexte de persistance, il ne peut exister quune seule instance
dentit avec le mme identifiant de persistance (si un Book didentifiant 1234
existe dans le contexte de persistance, aucun autre Book portant le mme
identifiant ne peut exister dans le mme contexte).
Seules les entits contenues dans le contexte de persistance sont gres par le
gestionnaire dentits (leurs modifications sont refltes dans la base).
Le gestionnaire dentits modifie ou consulte le contexte de persistance
chaque appel dune mthode de linterface EntityManager.
Exemple : - lorsque la mthode persist() est appele, lentit passe en
paramtre est ajoute au contexte de persistance si elle ne sy trouve pas.
- lorsquon recherche une entit avec son identifiant, le gestionnaire dentits
vrifie dabord si elle existe dj dans le contexte de persistance.
8
Contexte de persistance
Le contexte de persistance est considr comme un cache de premier niveau :
cest un espace rduit o le gestionnaire stocke les entits avant dcrire son
contenu dans la base. Les objets ne vivent dans le contexte de persistance que
le temps de la transaction.
Pour crer le gestionnaire dentits, on doit configurer une unit de persistance.
Ceci est dfinie dans META-INF/persistence.xml qui prcise les informations
ncessaires pour la connexion la base.
Lunit de persistance est le pont qui relie le contexte de persistance et la base.
Les marqueurs <class> donnent la liste des entits pouvant tre gres dans le
contexte de persistance .
Ici, on est dans un environnement gr par lapplication (transaction-type =
RESOURCE_LOCAL) alors que dans un environnement gr par un
conteneur, le fichier persistence.xml dfinirait une source de donnes la place
des infos de connexion et le type de transaction serait JTA (transaction-type =
JTA).
9
Manipulation des entits
Mthode Description
void refresh (Object entity) Rafraichit ltat de linstance partir de la base en
crasant les ventuelles modifications apportes lentit.
void flush() Synchronise le contexte de persistance avec la base de
donnes
void clear() Vide le contexte de persistance. Toutes les entits gres
deviennent dtaches
void clear(Object entity) Supprime lentit indique du contexte de persistance
Boolean contains(Object entity) Teste si linstance est une entit gre appartenant au
contexte de persistance courant
Soit une relation 1-1 unidirectionnelle entre Customer et son Address. Ces deux
entits ont des identifiants produits automatiquement, et Customer rcupre
lAddress de faon paresseuse (lorsquil en a besoin).
10
Manipulation des entits
Les deux entits prcdentes seront traduites dans la base de donnes avec la
structure prsente ci-dessous.
On note que la colonne ADRESS_FK est la colonne de type cl trangre
permettant daccder ADDRESS.
On suppose que lattribut em est de type EntityManager et que tx est de type
EntityTransaction.
assertNotNull(customer.getId());
assertNotNull(address.getId());
11
Rendre une entit persistante
partir de linstant dappel de em.persist(), les deux objets deviennent candidats
une insertion dans la base.
Lorsque la transaction est valide (tx.commit()), les donnes sont crites dans la
base.
Lentit Customer tant la propritaire de la relation, sa table contient une cl
trangre vers ADRESS.
Les deux expressions assertNotNull testent que les deux entits ont bien reu un
identifiant (fourni automatiquement par le fournisseur grce aux annotations).
On note lordre dappel des mthodes persist() : on rend le client persistant, puis
son adresse (mme si on avait fait linverse le rsultat est le mme car le
gestionnaire dentits met les donnes en cache et, lorsquil est prt, les crit
dans lordre quattend la base pour respecter les contraintes dintgrit).
cause de la cl trangre que contient la table CUSTOMER, linstruction insert
dans ADRESS doit seffectuer en premier, suivie de celle de CUSTOMER.
12
Suppression dune entit
EntityManager.remove() : supprime une entit qui est aussi te de la base,
dtache du gestionnaire dentits et qui ne peut plus tre synchronise avec la
base.
Customer customer = new Customer("Antony", "Balla", "tballa@mail.com");
Address address = new Address("Ritherdon Rd", "London", "8QE", "UK");
customer.setAddress(address);
tx.begin(); em.persist(customer); em.persist(address); tx.commit();
13
Synchronisation avec la base de donnes
14
Synchronisation avec la base de donnes
La mthode refresh() effectue une synchronisation dans la direction oppose
de flush() c.--d. quelle crase ltat courant dune entit gre avec les
donnes qui se trouvent dans la base.
On lutilise lorsquil sagit dannuler des modifications qui ont t effectues sur
lentit en mmoire.
Exemple : on recherche un client par son identifiant, et on modifie son prnom
et on annule ce changement en appelant refresh().
Customer customer = em.find(Customer.class, 1234L);
assertEquals(customer.getFirstName(), "Regragui");
customer.setFirstName("Ahmed");
em.refresh(customer);
assertEquals(customer.getFirstName(), "Regragui");
assertFalse(em.contains(customer));
On a un customer persistant. On peut vrifier que lentit est gre
(em.contains(customer) renvoie true). Puis on supprime cette entit de la base
et lappel de em.contains(customer) renvoie alors false.
15
Contenu du contexte de persistance
La mthode em.clear() vide le contexte de persistance : toutes les entits qui
taient gres deviennent dtaches.
em.clear(object entity) agit uniquement sur une entit.
em.detach(Object entity) supprime lentit indique du contexte de
persistance. Les modifications apportes cette entit ne sont plus
synchronises avec la base de donnes.
Customer customer = new Customer("Antony", "Balla", "tballa@mail.com");
tx.begin(); em.persist(customer); tx.commit();
assertTrue(em.contains(customer));
em.detach(customer);
assertFalse(em.contains(customer));
16
Contenu du contexte de persistance
Lorsquune entit est gre (non dtache), les modifications quon lui apporte
sont automatiquement refltes.
Si lentit nest pas gre, on doit appeler em.merge().
Customer customer = new Customer("Antony", "Balla", "tballa@mail.com");
tx.begin();
em.persist(customer);
customer.setFirstName("William");
tx.commit();
Lappel de la mthode customer.setFirstName() modifie ltat de lentit. Le
gestionnaire dentits met en cache toutes les actions excutes partir de
tx.begin() et ne les rpercute dans la base que lorsque la transaction est valide
avec tx.commit().
17
Contenu du contexte de persistance
Pour quune rpercussion ait lieu, lassociation de la relation doit tre modifie.
Les annotations @OneToOne, @OneToMany, @ManyToOne et
@ManyToMany disposent dun attribut cascade pouvant recevoir un tableau
dvnements propager.
On doit donc modifier lassociation de lentit Customer en ajoutant un attribut
cascade lannotation @OneToOne. Ici, on va propager persist et remove, afin
que la suppression dun client entrane celle de son adresse
@Entity
public class Customer {
@Id @GeneratedValue private Long id;
private String firstName; private String lastName; private String email;
@OneToOne (fetch = FetchType.LAZY, cascade= { CascadeType.PERSIST,
CascadeType.REMOVE })
private Address address;
// Constructeurs, getters et setters
}
2014/2015 Java Entreprise Edition : JEE 7 35
Type Description
PERSIST Propage les oprations persist la cible de la relation
REMOVE Propage les oprations remove la cible de la relation
MERGE Propage les oprations merge la cible de la relation
REFRESH Propage les oprations refresh la cible de la relation
CLEAR Propage les oprations clear la cible de la relation
ALL Propage toutes les oprations prcdentes
18
LAPI de cache
On a dj mentionn que le gestionnaire dentits est un
cache de premier niveau utilis pour traiter les donnes afin
quelle conviennent la base et mettre en cache les entits
en cours dutilisation.
LAPI de cache
19
LAPI de cache
Dans la figure prcdente, deux utilisateurs ont besoin d'accder des
entits dont les donnes sont stockes dans la base de donnes.
Chaque utilisateur a son propre contexte de persistance qui dure
pendant toute la dure de sa propre transaction.
Lutilisateur 1 obtient les entits identifies par 12 et 56 partir de la
base de donnes, afin que les deux soient stockes dans son contexte
de persistance.
Lutilisateur obtient les entits identifies par 12 et 34.
L'entit identifie par 12 est stocke dans le contexte de persistance de
chaque utilisateur. Bien que la transaction s'excute, le contexte de
persistance agit comme un cache de premier niveau pour stocker les
entits qui peuvent tre gres par lEntityManager. Une fois la
transaction est termine, le contexte de persistance se termine et les
entits sont effacs.
2014/2015 Java Entreprise Edition : JEE 7 39
20
JPQL : Java Persistence Query Language
On vient de voir comment manipuler sparment les entits avec
lEntityManager.
On sait rcuprer une entit partir de son identifiant, la supprimer, modifier
ses attributs, etc.
Mais rechercher une entit par son identifiant est assez limit, en pratique, on a
besoin de rcuprer une ou plusieurs entit(s) satisfaisant certaines conditions
(les clients qui habitent Rabat, ). Cette possibilit est propre aux bases de
donnes et JPA dispose du langage JPQL permettant ce type dinteractions.
JPQL sert dfinir des recherches dentits persistantes indpendamment de la
base de donnes correspondante.
Cest un langage de requte inspir de SQL (Structured Query Language).
La diffrence principale est que le rsultat de SQL est un ensemble de lignes
alors que celui de JPQL est une entit ou une collection dentits.
21
JPQL : select
SELECT <expression select>
FROM <clause from>
[WHERE <expression conditionnelle>]
[ORDER BY <clause order by>]
[GROUP BY <clause group by>]
[HAVING <clause having>]
La clause select porte sur une expression qui peut tre une entit, un attribut
dentit, une expression constructeur, une fonction agrgat ou toute squence
de ce qui prcde.
SELECT c.firstName, c.lastName
FROM Customer c // affiche une liste de prnom et de nom du client
Si lentit Customer est en relation 1-1 avec Address, c.address dsigne
ladresse du client .
SELECT c.adress
FROM Customer c // affiche une liste dadresses
JPQL : select
SELECT NEW com.apress.CustomerDTO( c.firstName, c.lastName , c.address.street1)
FROM Customer c
Le rsultat est une liste dobjets CustomerDTO instancis avec loprateur new
et initialiss avec le prnom, le nom et la rue des clients.
Pour supprimer des doublons, on utilise loprateur DISTINCT :
SELECT DISTINCT c.firstName
FROM Customer c
La clause SELECT peut utiliser les fonctions agrgat AVG, COUNT, MAX, MIN
et SUM. De plus, leurs rsultats peuvent tre regroups par une clause GROUP
BY et filtrs par une clause HAVING.
SELECT COUNT( c)
FROM Customer c
Les clauses SELECT, WHERE et HAVING peuvent utiliser expressions
scalaires (ABS, SQRT, MOD, ), des chaines (CONCAT, SUBSTRING,
LENGTH, ) et des dates (CURRENT_DATE, CURRENT_TIME,).
22
JPQL : from, where
La clause FROM dfinit les entits en dclarant des variables didentification ou
alias qui sont utiliss dans les autres clauses(select, where, ...).
La clause WHERE est forme dune expression conditionnelle permettant de
restreindre le rsultat dune instruction select, update ou delete.
SELECT c
FROM Customer c
WHERE c.firstName=REDA AND c.address.country=Maroc
La clause WHERE utilise les oprateurs de comparaison : =, >, <, <=, >=, <>,
[NOT] BETWEEN, [NOT] LIKE, [NOT] IN, IS [NOT] NULL, IS [NOT] EMPTY et
[NOT] MEMBER [OF]
SELECT c
FROM Customer c
WHERE (c.age NOT BETWEEN 40 AND 50) OR (c.email LIKE %mail.com)
JPQL : where
La clause WHERE nutilise pas que des valeurs fixes, mais aussi des
paramtres : par valeur ou par nom.
Les paramtres positionnels sont indiqus par un point dinterrogation suivi dun
entier (?1, par exemple).
Lorsque la requte sera utilise, il faut fournir les valeurs qui viendront
remplacer ces paramtres.
SELECT c
FROM Customer c
WHERE c.firstName=?1 AND c.address.country=?2
Les paramtres nomms sont reprsents par un identifiant de type String
prfix par le caractre deux-points (:). Lorsque la requte sera utilise, il faudra
fournir des valeurs ces paramtres nomms.
SELECT c
FROM Customer c
WHERE c.firstName= :fname AND c.address.country= :country
23
JPQL : sous requte
Une sous-requte est une requte SELECT intgre dans lexpression
conditionnelle dune clause WHERE ou HAVING.
Pour, obtenir les clients les plus jeunes de la base de donnes, on excute
dabord une sous-requte avec MIN(age) et lon value son rsultat dans la
requte principale :
SELECT c
FROM Customer c
WHERE c.age = (SELECT MIN(c.age) FROM CUSTOMER c)
JPQL : Order By
La clause ORDER BY permet de trier les entits ou les valeurs renvoyes par
une requte SELECT.
Le tri sapplique lattribut prcis dans cette clause. Si cet attribut est suivi
dASC ou daucun mot-cl, le tri sera ascendant; sil est suivi de DESC, le tri
sera descendant.
SELECT c
FROM Customer c
WHERE c.age >18
ORDER BY c.age DESC
SELECT c
FROM Customer c
WHERE c.age >18
ORDER BY c.age DESC, c.address.country ASC
24
JPQL : Group By et Having
La clause GROUP BY permet de regrouper des valeurs du rsultat en fonction
dun ensemble de proprits.
Les entits sont alors divises en groupes selon les valeurs de lexpression de la
clause GROUP BY.
Pour regrouper les clients par pays et les compter, on utilise la requte suivante :
SELECT c.address.country, count(c)
FROM Customer c
GROUP BY c.address.country
// HAVING count(c)>100 // les pays ayant plus de 100 clients
GROUP BY dfinit les expressions de regroupement (c.address.country) qui
serviront agrger et compter les rsultats.
On note que les expressions qui apparaissent dans GROUP BY doivent
apparaitre dans SELECT.
HAVING dfinit un filtre qui sapplique aprs le regroupement des rsultats, un
peu comme un second WHERE qui filtre le rsultat de GROUP BY(//exemple).
2014/2015 Java Entreprise Edition : JEE 7 49
25
JPQL : requtes
Comment intgrer dans une application les instructions JPQL ?
JPA 2.1 permet dintgrer cinq sortes de requtes :
Les requtes dynamiques. Il sagit de chanes de requtes JPQL indiques
dynamiquement au moment de lexcution.
Les requtes nommes. Des requtes statiques et non modifiables.
Les requtes natives. Elles permettent dexcuter une instruction SQL native
la place dune instruction JPQL.
API des critres : concept introduit par JPA 2.0.
Les requtes de procdures stockes: Nouveau avec JPA 2.1 pour appeler
les procdures stockes.
Le choix entre ces cinq types est centralis au niveau de linterface
EntityManager, qui dispose de plusieurs fabriques renvoyant toutes une
interface Query.
JPQL : requtes
Mthode Description
Query createQuery(String jpqlString) Cre une instance de Query permettant
dexcuter une instruction JPQL pour des
requtes dynamiques
Query createQuery(QueryDefinition qdef) Cre une instance de Query permettant
dexcuter une requte par critre
Query createNamedQuery(String name) Cre une instance de Query permettant
dexcuter une requte nomme (en JPQL ou
en SQL natif)
Query createNativeQuery(String sqlString) Cre une instance de Query permettant
dexcuter une instruction SQL native
Query createNativeQuery(String sqlString, Cre une instance de Query permettant
Class resultClass) dexcuter une instruction SQL native en lui
passant la classe du rsultat attendue
26
JPQL : requtes
Mthode Description
Query createNativeQuery(String Cre une instance de Query permettant
jpqlString, String resultSetMapping) dexcuter une instruction SQL en lui passant
un set Map du rsultat attendu.
<T> TypedQuery<T> createQuery Cre une instance de TypedQuery permettant
(CriteriaQueryDefinition <T> criteriaQuery) dexcuter une requte par critre
<T> TypedQuery<T> createQuery (String Cre une instance de TypedQuery permettant
jpqlString, Class<T> resultClass ) dexcuter une requte du rsultat attendu
JPQL : requtes
Mthode Description
StoredProcedureQuery Cre une StoredProcedureQuery pour
createStoredProcedureQuery (String excuter la procdure stocke dans
procedureName) la base.
StoredProcedureQuery Une requte de procdure stocke avec
createStoredProcedureQuery (String passage des classes pour lesquelles les
procedureName, Class... resultClasses) rsultats sont mapps
StoredProcedureQuery Une requte de procdure stocke avec
createStoredProcedureQuery (String passage des rsultats mapps
procedureName, String... resultSetMappings)
StoredProcedureQuery Cre une requte pour une procdure
createNamedStoredProcedureQuery (String stocke nomme
name)
27
JPQL : requtes
LAPI Query, est utilisable avec les requtes statiques (requtes nommes) et
les requtes dynamiques en JPQL, ainsi quavec les requtes natives en SQL.
Cette API permet galement de lier des paramtres aux requtes et de
contrler la pagination.
JPQL : requtes
28
JPQL : requtes
Les mthodes les plus utilises de cette API sont celles qui excutent la
requte.
Pour effectuer une requte SELECT, on doit choisir entre deux mthodes en
fonction du rsultat quon doit obtenir :
La mthode getResultList() excute la requte et renvoie une liste de rsultats
(entits, attributs, expressions, etc.).
La mthode getSingleResult() excute la requte et renvoie un rsultat unique.
Pour excuter une mise jour ou une suppression, on utilise la mthode
executeUpdate(), qui excute la requte et renvoie le nombre dentits
concernes par son excution.
Une requte peut prendre des paramtres nomms (:monParam) ou
positionnels (?2).
LAPI Query dfinit plusieurs mthodes setParameter() pour initialiser ces
paramtres avant lexcution dune requte.
JPQL : requtes
Une requte peut renvoyer un grand nombre de rsultats. Pour contrler la
pagination, les mthodes setFirstResult() et setMaxResults() permettent
respectivement dindiquer le premier rsultat (en partant de 0) et le nombre
maximal de rsultat.
Le mode flush indique au fournisseur de persistance comment grer les
modifications et les requtes en attente.
Deux modes sont possible : AUTO (par dfaut) et COMMIT. Le premier prcise
que cest au fournisseur de sassurer que les modifications en attente soient
visibles par le traitement de la requte.
COMMIT est utilis lorsque lon souhaite que leffet des modifications apportes
aux entits ncrase pas les donnes modifies dans le contexte de
persistance.
Les requtes peuvent tre verrouilles par setLockMode(LockModeType)
29
JPQL : requtes dynamiques
Les requtes dynamiques sont dfinies la vole par lapplication lorsquelle en
a besoin.
Elles sont cres par un appel la mthode em.CreateQuery(), qui prend en
paramtre une chane reprsentant une requte JPQL.
Query query =em.createQuery(SELECT c FROM Customer c );
List <Customer> customers = query.getResulList();
30
JPQL : requtes dynamiques
On peut passer le prnom Ilias en paramtre par nom ou par position.
jpqlQuery =SELECT c FROM Customer c ;
If(someCriteria) jpqlQuery += WHERE c.firstName = :fname ;
query =em.createQuery(jpqlQuery);
Query.setParameter(fname, Ilias );
List <Customer> customers = query.getResulList();
31
JPQL : requtes nommes
Les requtes nommes sont exprimes via une annotation @NamedQuery ou
son quivalent XML.
Cette annotation prend deux lments : le nom de la requte et son contenu.
On modifie lentit Customer pour dfinir trois requtes statiques :
@Entity
@NamedQueries({
@NamedQuery(name = "findAll", query = "select c from Customer c"),
@NamedQuery(name = "findVincent", query = "select c from Customer c where
c.firstName = 'Vincent'"),
@NamedQuery(name = "findWithParam", query = "select c from Customer c where
c.firstName = :fname") })
public class Customer { @Id @GeneratedValue private Long id;
private String firstName; private String lastName; private Integer age; private String email;
@OneToOne @JoinColumn(name = "address_fk") private Address address;
// Constructeurs, getters et setters
}
2014/2015 Java Entreprise Edition : JEE 7 63
32
JPQL : requtes nommes
Pour excuter la requte findAll :
Query query =em.createNamedQuery(findAll );
List <Customer> customers = query.getResulList();
Pour appeler la requte findWithParam en lui passant le paramtre fname et
en limitant le rsultat 3 :
Query query = em.createNamedQuery(findWithParam );
query.setParameter(fname, Ilias);
query.setMaxResults(3);
List <Customer> customers = query.getResulList();
On peut utiliser un raccourci qui permet dappeler
setParameter().setMaxResults(), etc.
Query query = em.createNamedQuery(findWithParam).setParameter(fname,
Ilias).setMaxResults(3);
33
JPQL : requtes natives
JPA autorise lutilisation des fonctionnalits spcifiques dun SGBDR via des
requtes natives. Celles-ci prennent en paramtre une instruction SQL et
renvoient une instance de Query.
Les requtes natives ne sont pas ncessairement portables dune base
lautre.
On prfre les requtes natives aux appels JDBC, qui sont portables, car le
rsultat de cette requte est automatiquement converti en entits.
Query query = em.createNativeQuery("Select * FROM t_customer ",
Customer.class);
List <Customer> customers = query.getResulList();
Les requtes natives peuvent utiliser le mcanisme des annotations pour dfinir
des requtes SQL statiques.
@Entity
@NamedNativeQuery(name = "findAll", query = "select * from t_customer")
@Table(name="t_customer")
public class Customer { // Attributs, constructeurs, getters et setters }
2014/2015 Java Entreprise Edition : JEE 7 67
34
Requtes de procdures stockes
UPDATE T_Transport
SET Warehouse_To_Take_Books_From = @warehouseCode;
END
35
Requtes de procdures stockes
36
JPQL : concurrence
JPA peut modifier des donnes persistantes et JPQL permet de rcuprer des
donnes rpondant certains critres.
Lapplication peut avoir plusieurs threads et une seule base de donnes : il est
donc assez frquent daccder aux entits de faon concurrente.
Lapplication doit contrler la synchronisation des donnes au moyen dun
mcanisme de verrouillage.
JPA 2.0 dispose de deux types de verrouillages (JPA 1.0 disposait seulement du
verrouillage optimiste) :
Le verrouillage optimiste. il repose sur la supposition que la plupart des
transactions nentrent pas en conflit les unes avec les autres, ce qui permet une
concurrence aussi permissive que possible.
Le verrouillage pessimiste. Il fait la supposition inverse, ce qui impose dobtenir
un verrou sur la ressource avant de la manipuler.
Exemple de traverse dune avenue. Dans une zone faible trafic, on peut
traverser sans regarder (traverse optimiste) alors que, dans une zone fort
trafic, il ne faut pas le faire (traverse pessimiste).
2014/2015 Java Entreprise Edition : JEE 7 73
JPQL : concurrence
Le verrouillage peut seffectuer au niveau du gestionnaire dentits et au niveau
Query avec les mthodes numres ci-dessous :
Mthode Description
<T> T find (Class <T> entityClass, Object Recherche une entit de la classe avec la cl
primaryKey, LockModetype lockMode ) indique et la verrouille selon le type du verrou
void lock(Object entity, LockModeType Verrouille une instance dentit contenue dans
lockMode) le contexte de persistance avec le type de
verrou indiqu
void refresh (Object entity, Rafrachit ltat de linstance partir de la base
LockModeType lockMode) en crasant les ventuelles modifications
apportes lentit et verrouille celle-ci selon le
type de verrou indiqu
Mthode Description
Query setLockMode(LockModetype Fixe le type de verrou utilis pour lexcution
lockMode ) de la requte
37
JPQL : concurrence
Toutes les mthodes prcdentes attendent un paramtre LockModeType qui
peut prendre les valeurs suivantes :
OPTIMISTIC. Verrouillage optimiste.
OPTIMISTIC_FORCE_INCREMENT. verrouillage optimiste et incrmentation
de la colonne version de lentit ( venir).
PESSIMISTIC_READ. Verrouillage pessimiste sans avoir besoin de relire les
donnes la fin de la transaction pour obtenir un verrou.
PESSIMISTIC_WRITE. verrouillage pessimiste et srialisation entre les
transactions pour mettre jour lentit.
PESSIMISTIC_FORCE_INCREMENT. Verrouillage pessimiste et
incrmentation de la colonne version de lentit ( venir).
NONE. Aucun mcanisme de verrouillage nest utilis
JPQL : concurrence
On peut lire puis verrouiller :
Book book =em.find(Book.class, 12);
// verrouille pour augmenter le prix
em.lock(book, LockModeType.PESSIMISTIC);
book.raisePriceByTwoDollars();
38
JPQL : gestion de version
Java utilise le systme des versions : 1.4, 1.5, 1.6, 1.7
JPA utilise le mme mcanisme lorsquon a besoin de versions dentits.
La premire fois quon rend une entit persistante, elle prend le numro de
version 1.
Si plus tard, on modifie un attribut et quon rpercute cette modification dans la
base de donnes, le numro de version de lentit passe 2, etc.
La version de lentit volue chaque fois quelle est modifie.
Pour ceci, lentit possde un attribut annot par @Version, lui permettant de
stocker son numro de version. Cet attribut est ensuite traduit par une colonne
dans la base.
Les types autoriss pour les numros de version sont int, Integer, short, Short,
long, Long ou Timestamp.
39
JPQL : gestion de version
Dans le code prcdent, on rend une nouvelle entit Book persistante.
Lattribut de version nest pas obligatoire, mais il est conseill lorsque lentit est
susceptible dtre modifie en mme temps par plusieurs processus ou
plusieurs threads.
La gestion de version est au cur du verrouillage optimiste car elle offre une
protection pour les modifications concurrentes des entits.
40
JPQL : verrouillage optimiste
Les transactions tx1 et tx2 obtiennent toutes les deux une instance de la mme
entit Book. ce moment la version de lentit est 1.
tx1 augmente le prix de 2$ et valide cette modification : lorsque les donnes sont
crites dans la base, le fournisseur incrmente le numro de version 2.
Si tx2 augmente le prix de 5$ et valide galement cette modification, le
gestionnaire dentits de tx2 ralisera que le numro de version dans la base est
diffrent de celui de lentit, ce qui signifie que lentit a t modifie, une
exception OptimisticLockException sera alors lance.
41
JPQL : verrouillage pessimiste
Consiste verrouiller systmatiquement lentit avant de la manipuler.
Ce mcanisme est trs restrictif et dgrade les performances de faon
significative puisquil implique que la base pose un verrou avec SELECT
FOR UPDATE lorsquelle lit les donnes.
Cest un mcanisme efficace pour garantir que deux clients ne modifient pas la
mme ligne en mme temps, mais il exige des vrifications de bas niveau qui
pnalisent les performances.
Les transactions qui violent cette contrainte provoquent la leve dune exception
PessimisticLockException et sont annules.
Si 100 millions de personnes veulent vendre leurs actions en mme temps, le
systme doit utiliser un verrouillage pessimiste pour assurer la cohrence des
donnes.
Le verrouillage pessimiste peut sappliquer aux entits qui ne sont pas annots
par @Version.
42
Cycle de
vie dune
entit
Examinons la figure ci-dessus, qui reprsente les tats que peut prendre une
entit Customer, ainsi que les transitions entre ces tats.
Lorsquon cre une instance de lentit Customer laide de new. Elle existe en
mmoire bien que JPA ne la connaisse pas.
Si lon nen fait rien, elle devient hors porte et finit par tre supprime par le
garbage, ce qui marque la fin de son cycle.
43
Cycle de vie dune entit
Un autre moyen de dtacher une entit, cest de la srialiser. En effet, les
entits doivent implmenter linterface Serializable pour passer par un rseau
afin dtre invoques distance ou pour traverser des couches afin de safficher
dans une couche prsentation-cette restriction est due java et non JPA.
Une entit srialise, qui passe par le rseau et est dsrialise comme un
objet dtach : pour la rattacher, il faut appeler em.merge().
Le cycle de vie dune entit se dcompose en quatre parties : persistance,
modification, suppression et chargement, qui correspondent aux oprations
quivalentes sur la base.
Chacune de ces parties est associe un vnement Pr et Post qui peut
tre intercept par le gestionnaire dentits pour appeler une mthode mtier qui
doit avoir t marque par lune des annotations suivantes :
Mthodes de rappel
Annotation Description
@PrePersist La mthode est appele avant lexcution em.persist()
@PostPersist La mthode est appele aprs que lentit soit devenue persistante. Si
lentit produit sa cl primaire (avec @GeneratedValue), sa valeur est
accessible dans la mthode
@PreUpdate La mthode est appele avant une opration de modification de lentit
dans la base (appel setters de lentit ou em.merge())
@PostUpdate La mthode est appele aprs une opration de modification de lentit
dans la base
@PreRemove La mthode est appele avant lexcution de em.remove()
@PostRemove La mthode est appele aprs la suppression de lentit
@PostLoad La mthode est appele aprs le chargement de lentit (par JPQL ou
em.find()) ou avant quelle soit rafrachie partir de la base. Il nexiste pas
dannotation @PreLoad car cela naurait aucun sens dagir sur une entit
qui na pas encore t construite.
44
Mthodes
de rappel
Avant dinsrer une entit dans la base, le gestionnaire dentits appelle la mthode
annote par @PrePersist. Si linsertion ne provoque pas dexception, lentit est rendue
persistante, son identifiant est cre, puis la mthode annote par @PostPersist est
appele.
Il en va de mme pour les mises jour (@PreUpdate et @PostUpdate) et les suppressions
(@PreRemove et @PostRemove).
Lorsquune entit est charge de la base (par em.find() ou JPQL), la mthode est annote
par @PostLoad est appele. Lorsque lentit dtache a besoin dtre fusionne, le
gestionnaire dentits doit dabord vrifier si la version en mmoire est diffrente de celle en
base (@PostLoad) et modifier les donnes (@PreUpdate et @PostUpdate) si cest le cas.
Mthodes de rappel
Outre les attributs, les constructeurs, les getters et les setters, les entits
peuvent contenir du code mtier pour valider leur tat ou calculer certains
attributs.
Ce code mtier peut tre plac dans des mthodes java classiques invoques
par dautres classes ou dans des mthodes de rappel (callbacks).
Dans le cas des mthodes de rappel, cest le gestionnaire dentits qui les
appelle automatiquement en fonction de lvnement qui t dclench.
Exemple : soit lentit Customer dfinit une mthode pour valider les donnes.
Elle vrifie les valeurs des attributs dateOfBirth et phoneNumber.
Cette mthode est annote par @PrePersist et @PreUpdate, elle sera appele
avant linsertion ou la modification des donnes dans la base.
Si ces donnes ne sont pas valides, la mthode lvera une exception
lexcution et linsertion ou la modification sera annule.
45
@Entity
public class Customer { @Id @GeneratedValue private Long id;
private String firstName; private String lastName; private String email;
private String phoneNumber;
@Temporal(TemporalType.DATE) private Date dateOfBirth;
@Transient private Integer age;
@Temporal(TemporalType.TIMESTAMP) private Date creationDate;
@PrePersist @PreUpdate
private void validate() {
if (dateOfBirth.getTime()>new Date().getTime() )
throw new IllegalArgumentException("Invalid date of birth");
if (!phoneNumber.startsWith("+"))
throw new IllegalArgumentException("Invalid phone number"); }
@PostLoad @PostPersist @PostUpdate
public void calculateAge() { if (dateOfBirth == null) { age = null; return; }
Calendar birth = new GregorianCalendar(); birth.setTime(dateOfBirth);
Calendar now = new GregorianCalendar(); now.setTime(new Date());
int adjust = 0;
if (now.get(DAY_OF_YEAR) - birth.get(DAY_OF_YEAR) < 0) { adjust = -1; }
age = now.get(Calendar.YEAR) - birth.get(Calendar.YEAR) + adjust; }
// Constructeurs, getters et setters
}
2014/2015 Java Entreprise Edition : JEE 7 91
Mthodes de rappel
calculateAge() calcule lge du client. Lattribut age est transitoire (nest pas crit
dans la base) : lorsque lentit est charge, rendue persistante ou modifie,
cette mthode calcule lge partir de la date de naissance et initialise lattribut.
Les mthodes de rappel doivent respecter les rgles suivantes :
Elles peuvent avoir un accs public, priv, protg ou paquetage, mais pas
statiques ni finales.
Elles peuvent tre marques par plusieurs annotations du cycle de vie.
Cependant, cet annotation ne peut apparatre quune seule fois dans une entit
(par exemple, pas deux fois une @PrePersist).
Elles peuvent lancer des exceptions non contrles mais pas dexceptions
contrles (si exception la transaction en cours est annule).
Elles peuvent invoquer JNDI, JDBC, JMS et les EJB, mais aucune opration
EntityQuery ou Query.
Avec lhritage, si une mthode de rappel est dfinie dans la superclasse, elle
sera appele avant la mthode de rappel de la classe fille.
2014/2015 Java Entreprise Edition : JEE 7 92
46
Mthodes de rappel
Si une relation utilise la rpercussion des vnements, la mthode de rappel
associe sera galement appele en cascade. Exemple : si Customer contient
une collection dadresses et que la suppression dun Customer soit rpercute
sur Address, la suppression dun client invoquera la mthode @PreRemove
dAddress et celle de Customer.
Les mthodes de rappel dune entit fonctionnent bien lorsque la logique mtier
nest lie qu cette entit.
Les couteurs (listeners) permettent dextraire cette logique dans une classe
spare qui pourra tre partage par plusieurs entits.
Un couteur dentit est simplement un POJO qui dfinit une ou plusieurs
mthodes de rappel du cycle de vie. Pour enregistrer un couteur, il suffit que
lentit utilise lannotation @EntityListeners.
Par rapport lexemple prcdent, on va extraire les mthodes calculateAge() et
validate() pour les placer respectivement dans deux classes couteurs,
AgeCalculationListener et DataValidationListener suivantes :
2014/2015 Java Entreprise Edition : JEE 7 93
couteurs (listeners)
public class AgeCalculationListener {
@PostLoad @PostPersist @PostUpdate
public void calculateAge(Customer customer) {
System.out.println("AgeCalculationListener calculateAge()");
if (customer.getDateOfBirth() == null) { customer.setAge(null); return; }
Calendar birth = new GregorianCalendar(); birth.setTime(customer.getDateOfBirth());
Calendar now = new GregorianCalendar(); now.setTime(new Date());
int adjust = 0;
if (now.get(DAY_OF_YEAR) - birth.get(DAY_OF_YEAR) < 0) { adjust = -1; }
customer.setAge(now.get(YEAR) - birth.get(YEAR) + adjust);
} }
public class DataValidationListener {
@PrePersist @PreUpdate
private void validate(Customer customer) {
if (dateOfBirth.getTime()>new Date().getTime() )
throw new IllegalArgumentException("Invalid date of birth");
if (!phoneNumber.startsWith("+"))
throw new IllegalArgumentException("Invalid phone number"); }
}
2014/2015 Java Entreprise Edition : JEE 7 94
47
couteurs (listeners)
Une classe couteur ne doit obir qu des rgles simples :
Elle doit avoir un constructeur public sans paramtre.
Les mthodes de rappel qui y sont dfinies ont deux signatures :
Si une mthode doit servir plusieurs entits, elle doit prendre un paramtre de
type Object. void <Methode> (Object uneEntity) .
Si elle nest destine qu une seule entit ou ses sous-classes, le paramtre
peut tre celui de lentit. void <Methode>(Customer customerOuSousClasse).
Pour indiquer que ces deux classes couteurs seront utilises comme des
vnements du cycle de vie de lentit Customer.
Lentit Customer doit prciser ceci avec lannotation @EntityListeners. Cette
annotation prend en paramtre une classe couteur ou un tableau dcouteurs.
Lorsquil y a plusieurs couteurs et quun vnement du cycle de vie survient, le
fournisseur de persistance parcourt chacun de ces couteurs dans lordre o ils
ont t indiqus et invoquera la mthode de rappel en lui passant une rfrence
lentit concerne par lvnement.
couteurs (listeners)
Puis il appellera les mthodes de rappel de lentit elle-mme (sil y en a).
@EntityListeners({DataValidationListener.class, AgeCalculationListener.class})
@Entity
public class Customer { @Id @GeneratedValue private Long id;
private String firstName; private String lastName; private String email;
private String phoneNumber;
@Temporal(TemporalType.DATE) private Date dateOfBirth;
@Transient private Integer age;
@Temporal(TemporalType.TIMESTAMP) private Date creationDate;
// Constructeurs , getters et setters
}
Ce code produit le mme rsultat que le code de la page 91.
Lentit Customer utilise la mthode DataValidationlistener.validate() pour
valider ses donnes avant toute insertion ou mise jour et la mthode
AgecalculationListener.calculateAge() pour calculer son ge.
48
couteurs (listeners)
Les rgles que doivent respecter les mthodes dun couteur sont les mmes
que celles suivies par les mthodes de rappel, mis part quelques dtails.
Elles ne peuvent lancer que des exceptions non contrles. C.--d. les autres
couteurs et mthodes de rappel ne seront pas appels et que lventuelle
transaction sera annule.
Dans une hirarchie de classes, si plusieurs entits dfinissent des couteurs,
ceux de la superclasse seront appels avant ceux des sous-classes. Si une
entit ne veut pas hriter des couteurs de sa superclasse, elle peut
explicitement les exclure laide dune annotation
@ExludeSuperclassListeners (ou son quivalent en XML).
Lentit Customer de la page prcdente dfinit deux couteurs, mais il est
possible quun couteur soit dfini par plusieurs entits.
Le code de la page suivante, cre un couteur de dbogage affichant le nom
des vnement dclenchs.
couteurs (listeners)
public class DebugListener {
@PrePersist
void prePersist(Object object) { System.out.println("++ prePersist"); }
@PreUpdate
void preUpdate(Object object) { System.out.println("++ preUpdate"); }
@PreRemove
void preRemove(Object object) { System.out.println("++ preRemove"); }
@PostLoad
void postLoad(Object object) { System.out.println("++ postLoad"); }
@PostRemove
void postRemove(Object object) { System.out.println("++ postRemove"); }
@PostUpdate
void postUpdate(Object object) { System.out.println("++ postUpdate"); }
@PostPersist
void postPersist(Object object) { System.out.println("++ PostPersist"); }
}
2014/2015 Java Entreprise Edition : JEE 7 98
49
couteurs (listeners)
Chaque mthode du code prcdent prend un Object en paramtre, ce qui signifie
que nimporte quel type dentit peut utiliser cet couteur en ajoutant la classe
DebugListener son annotation @EntityListeners.
Pour viter dajouter manuellement cette annotation chacune des entits de
lapplication, JPA permet de dfinir des couteurs par dfaut qui couvrent toutes
les entits dune unit de persistance.
Or il nexiste pas dannotation ayant la porte dune unit de persistance, cest
pourquoi ces couteurs par dfaut sont dclars dans un fichier dassociation
XML.
<?xml version="1.0" encoding="UTF-8"?>
<entity-mappings xmlns="http://java.sun.com/xml/ns/persistence/orm" version="2.0">
<persistence-unit-metadata> <persistence-unit-defaults>
<entity-listeners> <entity class="com.apress.javaee7.DebugListener">
</entity-listeners>
</persistence-unit-defaults> </persistence-unit-metadata>
</entity-mappings >
2014/2015 Java Entreprise Edition : JEE 7 99
couteurs (listeners)
Le marquer <persistence-unit-metadata> sert dfinir toutes les mtadonnes
qui nont pas dquivalent avec les annotations.
Le marquer <persistence-unit-defaults> dfinit toutes les valeurs par dfaut de
lunit de persistance.
<entity-listener> dfinit lcouteur par dfaut.
Ce fichier doit tre nomm persistence.xml et doit tre dploy avec
lapplication. DebugListener sera alors automatiquement appele par toutes les
entits.
Si on dfinit une liste dcouteurs par dfaut, chacun deux sera appel dans
lordre o il apparat dans le fichier XML.
Les couteurs par dfaut sont toujours invoqus avant ceux dfinis par
lannotation @EntityListeners. Pour quils ne sappliquent pas une entit
particulire, celle-ci doit le prciser avec lannotation
@ExcludeDefaultListeners.
50
couteurs (listeners)
@ExcludeDefaultListeners
@Entity
public class Customer {
@Id @GeneratedValue private Long id;
private String firstName;
private String lastName;
private String email;
private String phoneNumber;
@Temporal(TemporalType.DATE) private Date dateOfBirth;
@Transient private Integer age;
@Temporal(TemporalType.TIMESTAMP) private Date creationDate;
// Constructeurs, getters et setters
}
51