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