Java Persistence API (JPA)

Transcription

Java Persistence API (JPA)
Java Persistence API (JPA)
1
Introduction
La technologie JPA (Java Persistence API 1 ) a pour objectif d’offrir un modèle d’ORM (Object Relational
Mapping) indépendant d’un produit particulier (comme Hibernate, TopLink, etc.). Cette technologie est basée
sur
• un jeu d’interfaces et de classes permettant de séparer l’utilisateur d’un service de persistance (votre
application) et le fournisseur d’un service de persistance,
• un jeu d’annotations pour préciser la mise en correspondance entre classes Java et tables relationnelles,
• un fournisseur de persistance (par exemple Hibernate),
• un fichier XML « persistence.xml » décrivant les moyens de la persistance (fournisseur, datasource,
etc.)
Cette technologie est utilisable dans les applications Web (conteneur Web), ou dans les EJB (serveur d’applications) ou bien dans les applications standards (Java Standard Edition). C’est ce dernier cas que nous allons
étudier.
Quelques liens :
La page JPA chez Sun
http://java.sun.com/javaee/technologies/persistence.jsp
Javadoc de l’API JPA 1.0 (JEE 5)
http://java.sun.com/javaee/5/docs/api/javax/persistence/package-summary.html
Javadoc de l’API JPA 2.0 (JEE 6)
http://java.sun.com/javaee/6/docs/api/javax/persistence/package-summary.html
Le tutorial JPA 1.0 (JEE 5)
http://java.sun.com/javaee/5/docs/tutorial/doc/bnbpy.html
Le tutorial JPA 2.0 (JEE 6)
http://java.sun.com/javaee/6/docs/tutorial/doc/bnbpy.html
La page de Wikipedia
http://en.wikipedia.org/wiki/Java Persistence API
De très bons exemples
http://schuchert.wikispaces.com/EJB+3+and+Java+Persistence+API
http://www.java2s.com/Tutorial/Java/0355 JPA/Catalog0355 JPA.htm
Un petit manuel de référence
http://jszyzx.scu.edu.cn/resin-doc/amber/index.xtp
Une documentation sur JPQL
http://download.oracle.com/docs/cd/E13189 01/kodo/docs40/full/html/ejb3 overview query.html
1. http://java.sun.com/javaee/technologies/persistence.jsp
1
2
Mise en place
2.1
Projet Eclipse
Pour la mise en pratique de JPA, nous allons utiliser le framework Hibernate 2 qui va nous servir de fournisseur
de persistance. Plus précisement, suivez les étapes ci-dessous :
1. Créez un projet Java (pas JPA) dans Eclipse et préparez deux packages : monpkg.entities pour les
JavaBeans et monpkg.dao pour le service DAO.
2. Ajoutez Maven à votre projet : Sélectionnez votre projet / Bouton-droit / Configure / Convert to
Maven Project. Vous devez à cette étape donner une numéro de version à votre projet. Laissez les valeurs
par défaut.
3. Cherchez sur MVNRepository 3 le package Hibernate JPA Support et incluez dans le fichier pom.xml de
votre projet les dépendances pour la version 5.
4. Faites de même pour le pilote JDBC MySql 4 .
5. Vérifiez que la base de données est toujours opérationnelle et que vous pouvez créer des tables et insérer
des données. Profitez de cette étape pour préparer une console dans laquelle vous pourrez utiliser le client
classique de votre base de données afin de suivre les modifications.
6. Créez à la racine des fichiers sources Java le fichier META-INF/persistence.xml avec le contenu cidessous :
<persistence xmlns="http://java.sun.com/xml/ns/persistence"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/persistence
http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd"
version="1.0">
<persistence-unit name="myBase" transaction-type="RESOURCE_LOCAL">
<properties>
<!-- pour voir les requetes SQL -->
<property name="hibernate.show_sql" value="true" />
<property name="hibernate.format_sql" value="true" />
<!-- parametres de connection JDBC -->
<property name="hibernate.connection.driver_class"
value="com.mysql.jdbc.Driver" />
<property name="hibernate.connection.url"
value="jdbc:mysql://localhost/dbessai" />
<property name="hibernate.connection.username"
value="massat" />
<property name="hibernate.connection.password"
value="votre-mot-de-passe" />
<property name="hibernate.dialect"
value="org.hibernate.dialect.MySQL5InnoDBDialect" />
<property name="hibernate.hbm2ddl.auto"
value="update" />
</properties>
</persistence-unit>
</persistence>
Pour s’adapter au SGBDR, Hibernate utilise une couche logicielle spécifique à chaque SGBDR. C’est le
2. http://www.hibernate.org/
3. http://mvnrepository.com
4. http://mvnrepository.com/open-source/mysql-drivers
2
dialect 5 . Si vous n’utilisez pas MySQL, modifiez la propriété dialect .
La propriété hibernate.hbm2ddl.auto demande à Hibernate de générer automatiquement les tables
nécessaires au bon fonctionnement de la couche JPA (à noter que la valeur create-drop est aussi
intéressante) 6 .
2.2
Le service DAO
Nous commençons par un service vide pour tester l’environnement.
package monpkg.dao;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;
public class Dao {
private EntityManagerFactory factory = null;
public void init() {
factory = Persistence.createEntityManagerFactory("myBase");
}
public void close() {
if (factory != null) {
factory.close();
}
}
}
Explications : Lors de l’initialisation, la classe Persistence 7 est utilisée pour analyser les paramètres de
connection (fichier persistence.xml ) et trouver l’unité de persistance passée en paramètre (myBase dans
cet exemple). A l’issue de cette étape nous récupérons une instance de l’interface EntityManagerFactory 8 .
Cette usine, qui est généralement un singleton, nous permettra, dans un deuxième temps, d’ouvrir des connections
vers la base de données.
2.3
Test du service DAO
Créez une classe de test unitaire en vous basant sur Junit en version 4 :
5.
6.
7.
8.
http
http
http
http
://www.hibernate.org/hib docs/v3/reference/fr/html single/#configuration-optional-dialects
://www.hibernate.org/hib docs/v3/reference/fr/html single/#onfiguration-optional
://java.sun.com/javaee/6/docs/api/javax/persistence/Persistence.html
://java.sun.com/javaee/6/docs/api/javax/persistence/EntityManagerFactory.html
3
package monpkg.dao;
import static org.junit.Assert.*;
import
import
import
import
import
org.junit.After;
org.junit.AfterClass;
org.junit.Before;
org.junit.BeforeClass;
org.junit.Test;
public class TestDao {
static Dao dao;
@BeforeClass
public static void beforeAll() {
dao = new Dao();
dao.init();
}
@AfterClass
public static void afterAll() {
dao.close();
}
@Before
public void setUp() {
// pour plus tard
}
@After
public void tearDown() {
// pour plus tard
}
@Test
public void testVide() {
}
}
Explications : La classe Dao est instanciée une seule fois (annotation @BeforeClass ) car la création d’une
EntityManagerFactory 9 est très coûteuse. C’est typiquement une opération réalisée à l’initialisation de l’application.
Vérification : exécutez ce test et vérifiez son bon fonctionnement. Après cette étape nous avons vérifié les
paramètres et la connection avec le SGBDR.
3
Une première entité
Pour construire notre exemple, nous allons créer une entité personne (classe Person ) :
9. http ://java.sun.com/javaee/6/docs/api/javax/persistence/EntityManagerFactory.html
4
3.1
Le javaBean Person
package monpkg.entities;
import java.io.Serializable;
import java.util.Date;
import java.util.Set;
import
import
import
import
import
import
import
import
import
import
import
import
javax.persistence.Basic;
javax.persistence.Column;
javax.persistence.Entity;
javax.persistence.GeneratedValue;
javax.persistence.GenerationType;
javax.persistence.Id;
javax.persistence.PostUpdate;
javax.persistence.PreUpdate;
javax.persistence.Temporal;
javax.persistence.TemporalType;
javax.persistence.Transient;
javax.persistence.Version;
@Entity(name = "Person")
public class Person implements Serializable {
private static final long serialVersionUID = 1L;
@Id()
@GeneratedValue(strategy = GenerationType.AUTO)
private long id;
@Basic(optional = false)
@Column(name = "first_name", length = 200,
nullable = false, unique = true)
private String firstName;
@Basic()
@Temporal(TemporalType.DATE)
@Column(name = "birth_day")
private Date birthDay;
@Version()
private long version = 0;
@Transient
public static long updateCounter = 0;
public Person() {
super();
}
public Person(String firstName, Date birthDay) {
super();
this.firstName = firstName;
this.birthDay = birthDay;
}
@PreUpdate
public void beforeUpdate() {
System.err.println("PreUpdate of " + this);
}
@PostUpdate
public void afterUpdate() {
System.err.println("PostUpdate of " + this);
updateCounter++;
5
}
Explications : Il y a beaucoup de choses à expliquer sur cet exemple. Commençons par étudier les annotations :
@Entity Cette annotation indique que la classe est un EJB Entity qui va représenter les données de la base
de données relationnelle. Vous pouvez fixer un nom (attribut name ) qui, par défaut, est celui de la classe.
[JavaDoc 10 ]
@Id Cette annotation précise la propriété utilisée comme clef primaire. Cette annotation est obligatoire pour un
EJB Entity. [JavaDoc 11 ]
@GeneratedValue Cette annotation spécifie la politique de construction automatique de la clef primaire. [JavaDoc 12 ]
@Basic C’est l’annotation la plus simple pour indiquer qu’une propriété est persistante (c’est-à-dire gérée par
JPA). A noter que le chargement retardé d’une propriété est possible avec l’attribut fetch=FetchType.LAZY .
[JavaDoc 13 ]
@Column C’est l’annotation principale pour préciser la correspondance entre colonne d’une table et propriété
d’une classe. [JavaDoc 14 ]
@Temporal A utiliser pour le mapping des types liés au temps ( java.util.Date , java.sql.Date , java.sql.Time ,
java.sql.Timestamp et java.util.Calendar ). [JavaDoc 15 ]
@Transient Indique que la propriété n’est pas persistante. [JavaDoc 16 ]
@Version Indique la propriété à utiliser pour activer et gérer la version des données. Cette capacité est notamment utile pour implémenter une stratégie de concurrence optimiste. [JavaDoc 17 ]
@PreUpdate/@PostUpdate Deux exemples de callback. Voilà la liste des évènements récupérables : PostLoad 18 ,
PostPersist 19 , PostRemove 20 , PostUpdate 21 , PrePersist 22 , PreRemove 23 .
Vérification : Relancez le test unitaire et vérifiez la bonne création de la table.
3.2
Enrichir le service DAO
Nous pouvons maintenant enrichir notre service DAO avec les quatre actions classiques : ajout, lecture, modification et destruction. Les méthodes de la classe DAO vont ressembler à ceci :
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
http
http
http
http
http
http
http
http
http
http
http
http
http
http
://java.sun.com/javaee/6/docs/api/javax/persistence/Entity.html
://java.sun.com/javaee/6/docs/api/javax/persistence/Id.html
://java.sun.com/javaee/6/docs/api/javax/persistence/GeneratedValue.html
://java.sun.com/javaee/6/docs/api/javax/persistence/Basic.html
://java.sun.com/javaee/6/docs/api/javax/persistence/Column.html
://java.sun.com/javaee/6/docs/api/javax/persistence/Temporal.html
://java.sun.com/javaee/6/docs/api/javax/persistence/Transient.html
://java.sun.com/javaee/6/docs/api/javax/persistence/Version.html
://java.sun.com/javaee/6/docs/api/javax/persistence/PostLoad.html
://java.sun.com/javaee/6/docs/api/javax/persistence/PostPersist.html
://java.sun.com/javaee/6/docs/api/javax/persistence/PostRemove.html
://java.sun.com/javaee/6/docs/api/javax/persistence/PostUpdate.html
://java.sun.com/javaee/6/docs/api/javax/persistence/PrePersist.html
://java.sun.com/javaee/6/docs/api/javax/persistence/PreRemove.html
6
public Person addPerson(Person p) {
EntityManager em = null;
try {
em = factory.createEntityManager();
em.getTransaction().begin();
// utilisation de l’EntityManager
em.persist(p);
em.getTransaction().commit();
System.err.println("addPerson witdh id=" + p.getId());
return (p);
} finally {
if (em != null) {
em.close();
}
}
}
Explications : Pour agir sur les entités nous devons récupérer à partir de l’usine une instance de l’interface
EntityManager 24 . Celle-ci va permettre les opérations CRUD de persistance sur les entités (Create, Read,
Update et Delete). Un EntityManager 25 ne supporte pas le multi-threading. Il doit donc être créé et détruit à
chaque utilisation par un thread. Cette création est une opération peu coûteuse qui peut se reproduire un grand
nombre de fois (contrairement à l’usine).
Vérification 1 : Ajoutez un test unitaire afin de valider la création d’une personne. Vérifiez dans la table les
informations ajoutées avec le client ligne de commande de MySql.
Vérification 2 : Testez la génération des erreurs dues à deux personnes ayant le même prénom (remarquez la
clause unique de la propriété firstName ).
3.3
Compléter le service DAO
Compléter l’implantation. Vous pouvez maintenant implémenter les méthodes ci-dessous en utilisant les méthodes
de l’interface EntityManager 26 (notamment persist pour l’ajout et merge pour la mise à jour).
public Person findPerson(long id) {
...
}
public Person updatePerson(Person p) {
...
}
public Person removePerson(long id) {
...
}
Tests. Enrichir la classe de test unitaire pour créer une personne, la recharger, la modifier et finalement la détruire.
4
Configurer la table
L’annotation Table 27 permet de préciser le nom de la table associée à une classe ainsi que d’ajouter des conditions
d’unicité.
24.
25.
26.
27.
http
http
http
http
://java.sun.com/javaee/6/docs/api/javax/persistence/EntityManager.html
://java.sun.com/javaee/6/docs/api/javax/persistence/EntityManager.html
://java.sun.com/javaee/6/docs/api/javax/persistence/EntityManager.html
://java.sun.com/javaee/6/docs/api/javax/persistence/Table.html
7
...
@Entity(name = "Person")
@Table(name = "TPerson",
uniqueConstraints = {
@UniqueConstraint(columnNames = {
"first_name", "birth_day"
})
})
public class Person implements Serializable {
...
Dans cet exemple, nous ajoutons une contrainte d’unicité sur le couple (prénom, date de naissance).
Vérification : Ajoutez un test unitaire afin de valider cette contrainte (vous devez enlever la clause unique sur
le prénom et détruire la table pour permettre une nouvelle création).
5
5.1
Les requêtes en JPA
Requêtes simples
Ajoutez à votre service DAO une méthode de listage des personnes :
@SuppressWarnings("unchecked")
public List<Person> findAllPersons() {
EntityManager em = null;
try {
em = factory.createEntityManager();
em.getTransaction().begin();
// utilisation de l’EntityManager
TypedQuery<Person> q = em.createQuery("FROM Person", Person.class);
return q.getResultList();
} finally {
if (em != null) {
em.close();
}
}
}
Travail à faire : En utilisant cette présentation 28 , la JavaDoc de l’interface Query 29 (ou en JPA 2 TypedQuery 30 )
et cette présentation 31 du langage JPQL (très proche de HQL), proposez une version permettant de sélectionner
les personnes à partir de leur prénom (clause like ).
5.2
Requêtes nommées
Vous pouvez aussi utiliser l’annotation NamedQuery 32 pour placer les requêtes dans les classes d’entité et y faire
référence dans les EJB.
28.
29.
30.
31.
32.
http://schuchert.wikispaces.com/JPA+Tutorial+2+-+Working+with+Queries+1
http ://java.sun.com/javaee/6/docs/api/javax/persistence/Query.html
http ://java.sun.com/javaee/6/docs/api/javax/persistence/TypedQuery.html
http://download.oracle.com/docs/cd/E11035 01/kodo41/full/html/ejb3 langref.html
http ://java.sun.com/javaee/6/docs/api/javax/persistence/NamedQuery.html
8
Travail à faire : Créez une requête particulière par l’annotation NamedQuery 33 est utilisez-la grace à la méthode
createNamedQuery (version typée JPA 2) de l’interface EntityManager 34 .
5.3
Création d’objets
Nous avons souvent besoin de récupérer une partie des propriétés d’une entité. Par exemple le numéro et le prénom
de chaque personne pour dresser une liste. Pour ce faire, nous devons créer un javaBean monpkg.entities.Result
qui représente les informations voulues et utiliser la requête suivante :
SELECT new monpkg.entities.Result(p.id,p.firstName)
FROM Person p
travail à faire : Créez le nouveau JavaBean et prévoyez un constructeur vide ainsi qu’un constructeur à deux
arguments. Ajoutez une méthode qui renvoie la liste des personnes et testez-la.
6
Les classes embarquées
Les classes embarquées permettent de coder plus facilement des relations un-un comme par exemple, le fait que
chaque personne doit avoir une et une seule adresse. Dans ce cas il est plus simple de placer l’adresse dans la
table qui code les personnes sans être obligé de créer une nouvelle table.
Définissons la classe Address et indiquons qu’elle peut être embarquée avec l’annotation Embeddable 35 :
33. http ://java.sun.com/javaee/6/docs/api/javax/persistence/NamedQuery.html
34. http ://java.sun.com/javaee/6/docs/api/javax/persistence/EntityManager.html
35. http ://java.sun.com/javaee/6/docs/api/javax/persistence/Embeddable.html
9
package monpkg.entities;
import java.io.Serializable;
import javax.persistence.Embeddable;
@Embeddable()
public class Address implements Serializable {
private
private
private
private
static
String
String
String
final long serialVersionUID = 1L;
street;
city;
country;
public Address() {
super();
}
public Address(String street, String city, String country) {
super();
this.street = street;
this.city = city;
this.country = country;
}
public String getStreet() {
return street;
}
public void setStreet(String street) {
this.street = street;
}
public String getCity() {
return city;
}
public void setCity(String city) {
this.city = city;
}
public String getCountry() {
return country;
}
public void setCountry(String country) {
this.country = country;
}
@Override
public String toString() {
return "Address(" + street + "," + city + "," + country + ")";
}
}
Nous pouvons maintenant embarquer cette adresse dans la classe Person en utilisant l’annotation Embedded 36 .
Nous retrouverons dans la table TPerson les colonnes nécessaires pour coder la rue, la ville et le pays.
36. http ://java.sun.com/javaee/6/docs/api/javax/persistence/Embedded.html
10
...
@Embedded
private Address address;
...
public Address getAddress() {
return address;
}
public void setAddress(Address address) {
this.address = address;
}
...
Complément : Si nous voulions associer deux adresses à une seule personne, alors nous devrions changer le nom
des colonnes qui codent la rue, la ville et le pays de la deuxième adresse. Pour ce faire, nous pouvons utiliser
l’annotation AttributeOverrides 37 (voir Embedded 38 ).
7
Relation Un-Plusieurs
Pour étudier la gestion des relations, nous allons créer une nouvelle entité Voiture (classe Car ) et une relation
père-fils entre une personne et plusieurs voitures. Le code de la classe Car est le suivant :
37. http ://java.sun.com/javaee/6/docs/api/javax/persistence/AttributeOverrides.html
38. http ://java.sun.com/javaee/6/docs/api/javax/persistence/Embedded.html
11
package monpkg.entities;
import java.io.Serializable;
import
import
import
import
import
javax.persistence.Column;
javax.persistence.Entity;
javax.persistence.Id;
javax.persistence.JoinColumn;
javax.persistence.ManyToOne;
@Entity
public class Car implements Serializable {
// default serial UID
private static final long serialVersionUID = 1L;
// properties
@Id()
private String immatriculation;
@Column(length = 150, nullable = false)
private String modele;
@ManyToOne(optional = true)
@JoinColumn(name = "owner")
private Person owner;
public Car() {
super();
}
public Car(String immatriculation, String modele) {
super();
this.immatriculation = immatriculation;
this.modele = modele;
}
@Override
public String toString() {
return "Car(" + immatriculation + ")";
}
public String getImmatriculation() {
return immatriculation;
}
public void setImmatriculation(String immatriculation) {
this.immatriculation = immatriculation;
}
public String getModele() {
return modele;
}
public void setModele(String modele) {
this.modele = modele;
}
public Person getOwner() {
return owner;
}
public void setOwner(Person owner) {
this.owner = owner;
}
12
Explications : L’annotation ManyToOne 39 indique que la propriété code une relation fils-vers-le-père et l’annotation JoinColumn 40 permet de donner un nom à la colonne de la table qui va jouer ce rôle. Attention pour
manipuler cette relation, vous devrez passer obligatoirement par la modification de cette propriété. C’est donc le
fils qui abrite ( owning ) cette relation.
Modifier la classe Person : La liaison dans l’autre sens (père-vers-les-fils) est codée par une annotation
OneToMany 41 appliquée à une collection de voitures. L’attribut mappedBy est particulièrement important car
il indique le nom de la propriété (côté fils) qui code la relation un-plusieurs.
...
@OneToMany(cascade = { CascadeType.MERGE, CascadeType.PERSIST },
fetch = FetchType.LAZY, mappedBy = "owner")
private Set<Car> cars;
...
public Set<Car> getCars() {
return cars;
}
public void setCars(Set<Car> cars) {
this.cars = cars;
}
...
Travail à faire :
• Le lien père-vers-les-fils est géré en mode retardé ( LAZY ). Modifiez la méthode findPerson pour être
sûr que les voitures soient chargées (utilisez simplement la méthode size() sur la collection de voitures
pour forcer le chargement).
• modifiez votre classe de test unitaire pour ajouter des voitures (en insertion de personne et en mise à jour
de personne).
8
Relation Plusieurs-Plusieurs
Nous allons mettre en place une relation plusieurs-plusieurs entre les personnes et des films. Pour ce faire, nous
allons ajouter la classe Movie définie comme suit :
39. http ://java.sun.com/javaee/6/docs/api/javax/persistence/ManyToOne.html
40. http ://java.sun.com/javaee/6/docs/api/javax/persistence/JoinColumn.html
41. http ://java.sun.com/javaee/6/docs/api/javax/persistence/OneToMany.html
13
package monpkg.entities;
import java.io.Serializable;
import
import
import
import
javax.persistence.Column;
javax.persistence.Entity;
javax.persistence.GeneratedValue;
javax.persistence.Id;
@Entity
public class Movie implements Serializable {
// for serialization
private static final long serialVersionUID = 1L;
// primary key
@Id()
@GeneratedValue()
private Long id;
@Column(length = 150, nullable = false)
private String name;
public Movie() {
super();
}
public Movie(String name) {
super();
this.name = name;
}
@Override
public String toString() {
return "Movie(" + id + "," + name + ")";
}
@Override
public boolean equals(Object obj) {
if (obj instanceof Movie)
return (((Movie) obj).getId() == id);
return false;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
14
Travail à faire : en vous basant sur ces exemples 42 , modifiez la classe Person en ajoutant le code suivant :
...
@ManyToMany(fetch = FetchType.LAZY,
cascade = { CascadeType.MERGE, CascadeType.PERSIST }
)
@JoinTable(
name = "Person_Movie",
joinColumns = { @JoinColumn(name = "id_person") },
inverseJoinColumns = { @JoinColumn(name = "id_movie") }
)
Set<Movie> movies;
...
public Set<Movie> getMovies() {
return movies;
}
public void setMovies(Set<Movie> movies) {
this.movies = movies;
}
public void addMovie(Movie movie) {
...
}
...
Les annotations ManyToMany 43 et JoinTable 44 vont nous permettre de définir cette relation et de créer la table
de correspondance. Dans notre exemple, la relation n’est accessible que via la classe Person . Nous aurions pu
également la définir dans la classe Movie en inversant les attributs joinColumns et inverseJoinColumns .
Attention : c’est la classe contenant l’annotation JoinTable 45 qui abrite (owning ) la relation. Toute modification
de cette relation doit donc passer par cette classe. En d’autres termes, si vous souhaitez ajouter un film à une
personne, vous devez charger la personne, puis le film et ajouter le film à la personne.
Vérification : Ajoutez un test unitaire afin de vérifier l’ajout d’un film à une personne et sa suppression.
9
Relation Un-Un
La relation Un-Un (où l’un des côtés peut être optionnel) permet de lier l’existence de deux entités. Cette relation
passe par l’annotation OneToOne 46 . La JavaDoc présente deux alternatives que nous ne détaillerons pas dans
cette séance.
Vous pouvez, à titre d’exemple, associer une personne à un CV (et vice-versa) mais en autorisant les personnes
sans CV (et non pas les CV sans personne). Pour ce faire, vous devez créer une entité CV et mettre en place les
liaisons.
42.
43.
44.
45.
46.
http://www.java2s.com/Tutorial/Java/0355 JPA/0200 Many-To-Many-Mapping.htm
http ://java.sun.com/javaee/6/docs/api/javax/persistence/ManyToMany.html
http ://java.sun.com/javaee/6/docs/api/javax/persistence/JoinTable.html
http ://java.sun.com/javaee/6/docs/api/javax/persistence/JoinTable.html
http ://java.sun.com/javaee/6/docs/api/javax/persistence/OneToOne.html
15
10
Traitement de la concurrence
10.1
Pose de verrou
Cette approche classique consiste à verrouiller les lignes (donc les objets) de la base après leur récupération pour
pouvoir les modifier à sa guise sans craindre qu’une autre transaction ne les altère. Pour ce faire nous allons
ajouter un paramètre au chargement 47 :
public void changeFirstName(long idPerson, String firstName) {
EntityManager em = null;
try {
em = factory.createEntityManager();
Person p = em.find(Person.class, idPerson);
em.lock(p, LockModeType.WRITE);
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
}
p.setFirstName(firstName);
em.getTransaction().commit();
} finally {
if (em != null) {
em.close();
}
}
}
Vérification : Vous pouvez tester ce code en l’exécutant plusieurs fois ou en essayant une modification via le
client du SGBDR.
10.2
Gestion optimiste
Dans la gestion optimiste, les données sont versionnées et JPA va vérifier que le numéro de version ne change
pas entre la lecture et la modification. Ce mécanisme est utile quand l’objet reste longtemps en mémoire entre
sa lecture et sa modification (par exemple pour un formulaire de saisie).
Nous avons déjà prévu une propriété version dans le JavaBean Person (annotation Version 48 ).
Vérification : Créez un test unitaire composé de trois parties :
•
•
•
•
lecture d’une personne dans p1,
lecture de la même personne dans p2,
changement de p2 et mise à jour,
changement de p1 et mise à jour,
Vous devriez obtenir une erreur d’exécution indiquant un problème dans la gestion optimiste du verrou : le numéro
de version a changé lors de la sauvegarde de p1.
11
Conseils
Après discussion avec plusieurs étudiants et pour éviter des blocages dus à des poses de verrous, je vous conseille
de renforcer la phase de fermeture des EntityManager en utilisant le code ci-dessous :
47. http ://www.hibernate.org/hib docs/v3/reference/fr/html single/#transactions-locking
48. http ://java.sun.com/javaee/6/docs/api/javax/persistence/Version.html
16
private EntityManager newEntityManager() {
EntityManager em = factory.createEntityManager();
em.getTransaction().begin();
return (em);
}
private void closeEntityManager(EntityManager em) {
if (em != null) {
if (em.isOpen()) {
EntityTransaction t = em.getTransaction();
if (t.isActive()) {
try {
t.rollback();
} catch (PersistenceException e) {
}
}
em.close();
}
}
}
17