Blue Flower

Chercher

1. L' API JPA

1.1. Généralités

L'API JPA, fait partie de la spécification des EJB 3.0 dans Java Enterprise Edition 5 (Java EE 5); elle est développée par Sun pour offrir des fonctionnalités génériques à tous les ORMs (Object Relational Mapping), ce qui le rend indépendante de tout ORM particulier (comme Hibernate, TopLink, etc); cette API JPA  est basée sur:

  • un ensemble d'interfaces et de classes qui permettent de séparer l'utilisateur d'un service de persistance (votre application) et son fournisseur,
  • un ensemble d'annotations pour préciser la mise en correspondance entre classes Java et tables d'une base de données relationnelles,
  • un fournisseur de persistance (par exemple Hibernate),
  • un fichier XML « persistence.xml » qui décrit les moyens de la persistance (fournisseur, datasource, etc.)

On utilise l'API JPA pour développer des  applications java, au sein  de serveurs d'application (applications Java EE) ou des applications Java SE (Java Standard Edition) permettant d' inter-agir avec des sources de données. L'un des éléments de base  l'API JPA  est le "JPA Entity" (identique aux beans Entity); au sein d'une application, une classe est considérée comme une JPA Entity si elle est précédée de l'annotation  @Entity  ou si elle est configurée dans un fichier XML (dans ce cas on parle  de meta information de persistance).

Dans la plupart des architectures applicatives, une couche d’accès aux données appelée DAO (Data Access Object) est modélisée; dans une configuration classique, cette couche interroge directement le SGBDR à l’aide de  pilote JDBC. Dans une configuration utilisant la spécification JPA couplée à un ORM qui l’implémente, le cheminement des informations entre la DAO et le SGBDR se trouve modifié. En effet, la couche DAO dialogue, à travers la spécification JPA, avec l’ORM qui fait ensuite le lien avec le SGBDR. L’ORM se charge du pont entre le monde relationnel de la base de données et les objets Java de l’application. L’ensemble des objets gérés par la couche JPA à travers ce pont en l'occurrence les JPA Entity, forme «le contexte de persistance». Au cours du cycle de vie de ce  contexte de persistance, un objet JPA Entity peut prendre différents états:   

  • Etat «persisté»: on parle d'état persisté  lorsque l’objet est géré par le contexte de persistance. 
  • Etat  «non persisté»: en opposition à «persisté», cet état signifie qu’un objet n’est pas géré par le contexte de persistance.  
  • Etat «détaché»: on parle d'état détaché dans le cas d’un objet déjà persisté mais dont le contexte de persistance a été fermé.

Pour gérer le cycle de vie du contexte de persistance, la couche JPA dispose de l’interface «EntityManager»; cette interface possède un certain nombre de méthodes et les plus utilisées sont:

  • void persist(Object entity); cette méthode insère  l'objet dans le contexte de persistance. La méthode persist ne se contente pas d'enregistrer une entité en base, elle positionne également la valeur de l'attribut représentant la clé de l'entité. La détermination de la valeur de la clé dépend de la stratégie spécifiée par @GeneratedValue. L'insertion en base ne se fait pas nécessairement au moment de l'appel à la méthode persist (on peut toutefois forcer l'insertion avec la méthode EntityManager.flush()). Cependant, l'EntityManager garantit que des appels successifs à sa méthode find(...) permettront de récupérer l'instance de l'entité.
    C'est une erreur d'appeler la méthode EntityManager.persist(...) en lui passant comme paramètre une entité dont l'attribut représentant la clé est non null. La méthode lève alors l'exception EntityExistsException.
  • void remove(Object entity); supprime l'objet du contexte de persistance.
  • <T> T merge(T entity); fusionne l'objet détaché avec celui de même identifiant présent dans le contexte de persistance  courant. La méthode EntityManager.merge(T) est parfois considérée comme la méthode permettant de réaliser les UPDATE des entités en base de données. Il n'en est rien et la sémantique de la méthode merge est très différente. En fait, il n'existe pas à proprement parler de méthode pour réaliser la mise à jour d'une entité. Un EntityManager surveille les entités dont il a la charge et réalise les mises à jour si nécessaire au commit de la transaction.
  • <T> T  find(class<T> entityClass, Object primaryKey); ajoute dans le contexte de persistance l'objet correspondant à cette clé primaire dans la base; le type T permet à la couche JPA de savoir sur quelle  table on fait la requête; en retour on a l'objet trouvé. La méthode EntityManager.find (Class<T>, Object) permet de rechercher une entité en donnant sa clé primaire. Un appel à cette méthode ne déclenche pas forcément une requête SELECT vers la base de données.
    En effet, une instance  EntityManager agit également comme un cache au dessus de la base de données qui garantit l'unicité des instances des objets. Si la méthode "find" est appelée plusieurs fois sur la même instance d'un EntityManager avec une clé identique, alors l'instance retournée est toujours la même.

L'interface EntityManager possède d’autres méthodes qui permettent d’exécuter des requêtes spécifiques sur la base. Ces requêtes peuvent être écrites en JPQL (Java Persistence Query Language) ou en SQL natif; on peut aussi utiliser l'API Criteria pour faire des requêtes sur des  objets JPA Entity.  On  peut également  centraliser les requêtes dans des fichiers de configuration externes.

Le langage JPQL, à l’inverse du SQL, exécute ses requêtes sur des objets et non sur des tables. Les similitudes entre les deux langages sont nombreuses. Cependant, le fait que les requêtes JPQL ne soient plus directement liées au SGBDR offre une plus grande portabilité.

2. Bean Entity

2.1. Généralités

La notion de JPA Entity est l'élément central de l'API JPA et dans une application java utilisant JPA, chaque bean Entity( JPA Entity) représente  une table dans une base de données relationnelle; chaque instance d'un bean Entity représente une ligne de la table associée au bean Entity dans la base de données. La persistance d'une instance d'un bean Entity est effective dans la base de données à travers les champs et les propriétés de celle-ci. Ces champs et ces propriétés servent pour mapper le bean Entity à une table dans la base de données.

2.2.  Les exigences pour un bean Entity

Un bean Entity doit obéir aux exigences suivantes:

  • Il doit être annoté par l'annotation  "@javax.persistence.Entity".
  • Il doit avoir un constructeur public ou privé sans argument; toutefois il peut avoir d'autres constructeurs.
  • Il ne doit pas être déclaré final ou avoir de méthode ou une instance déclarée final.
  • Si une instance d'un bean Entity est transmise par valeur en tant qu'un objet détaché à une autre méthode de l'application en cours, par exemple via un bean Session qui implémente une business interface, le bean Entity doit implémenter l'interface Serializable.
  • Un bean Entity peut étendre toute sorte de  classe java et vice-versa.
  • Les variables d'instance persistantes doivent être déclarées private ou  protected et peuvent être directement accessibles seulement par les méthodes du bean Entity. On accède à une instance du bean Entity par les accesseurs (Getter/setter) ou par les business méthodes.

2.3. Gestion d'un bean Entity

Un bean Entity au sein d'une application Java est  prise en charge par une instance de l'interface  "javax.persitence.EntityManger" et chaque instance de EntityManager est associée à un contexte de persistance; c'est dans le contexte de persistance que des instances des beans Entity sont créées, rendues persistantes ou supprimées. Dans l'interface EntityManager  sont définies des méthodes qui permettent d'agir sur les instances des beans Entity.

2.3.1. L'interface EntityManager

Comme on l'a dit ci-dessus, une instance de l'interface EntityManager  permet d'interagir avec l'unité de persistance d'une application Java utilisant l'API JPA; on  peut au sein d'une unité de persistance  créer, détruire, trouver dans une base de données relationnelles via la clé primaire  des instances de beans Entity. Une instance de EntityManager est soit traitée au sein d'un serveur d'applications Java soit au sein d'une simple application Java SE.

2.3.2. Bean Entity géré par un conteneur

Dans un serveur d'applications, le contexte de persistance est propagé automatiquement par le serveur d'applications à tous les composants de l'application impliquant une instance de l'interface EntityManager lors d'une transaction  JTA (Java Transaction API - c'est une API dédiée à la gestion de transaction). Pour utiliser une instance de l'interface EntityManager, on l'injecte au sein de l'application; le composant de l'application au sein duquel on fait l'injection peut être un EJB ou un composant Java EE géré par CDI. on réalise cette injection par l'annotation "@PersistenceContext" comme suit:

1) Injection d'un EntityManager dans un EJB

import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.ejb.Stateless;
@Stateless
public class MaClasse {
@PersistenceContext(unitName="MaPersistenceUnit")
private EntityManager entityManager;
...

}

On voit bien qu'on est en présence d'un EJB Stateless ( voir l' annotation) et au sein de cet EJB on injecte l'EntityManager en le précédant de l'annotation "@PersistenceContext(...).

2) Injection d'un EntityManager dans un composant Java EE géré par CDI

import javax.enterprise.context.RequestScoped;
import javax.inject.Named;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;

/*
* Notez que ce composant Java EE porte l'annotation @RequestScope.
* Il est donc possible d'injecter en toute sécurité un EntityManager.
*/
@Named
@RequestScoped
public class MaClasse {
@PersistenceContext(unitName="MaPersistenceUnit")
private EntityManager entityManager;
...
}

Le serveur d'application initialise et ferme les différentes instances d'EntityManagerFactory et d'EntityManager. Autrement dit, le développeur d'application Java EE n'a pas à se préoccuper d'appeler les méthodes close() sur ces instances.

2.3.3. Bean Entity géré par une application

On peut avoir une application Java qui gère elle même chaque contexte de persistance et le cycle de vie des instances de l'interface EntityManager. Dans ce cas, chaque EntityManager crée un nouveau contexte de persistance isolé. L'EntityManager et son contexte de persistance associé sont créés et détruits explicitement par l'application; toutefois il faut tenir compte du faite que les instances d'EntityManager ne sont pas thread-safe contrairement aux instances "EntityManagerFactory"; donc pour créer des instances d'EntityManager on utilise la méthode "createEntityManager()"; on commence par injecter une instance de EntityManagerFactory  à l'aide de l'annotation "PersistenceUnit" (voir code ci-dessous). Une fois qu'on a l'instance de EntityManagerFactory disponible, on l'utilise (une de ses méthodes en l'occurrence "createEntityManager()") pour créer une instance de l'interface EntityManager.

Le Contexte de persistance géré par l'application peut être utilisé avec JTA (Les transactions JTA se réfèrent aux transactions au sein d'un serveur d'applications  JEE) ou avec  la gestion des transactions par "Resource Local" (i.e une seule ressource transactionnelle, par exemple une connexion JDBC).
Tout d'abord, dans l'exemple ci-dessous on va illustrer la gestion des transactions par "Resource Local". Dans ce cas, le contexte de persistance est configuré comme RESOURCE_LOCAL; cela est précisé dans le fichier "persistence.xml" comme suit:

<persistence-unit name = "persistenceUnit" 
 transaction-type =RESOURCE_LOCAL">
  <non-jta-data-source> jdbc/dataSource </non-jta-data-source> 
   <class> test.domain.TestEntity </class> 
 </persistence-unit> 

Une fois ces précisions faites dans le fichier "persistence.xml", on obtient une instance de EntityManager à partir d'une instance de EntityManagerFactory comme suit:

@PersistenceUnit
EntityManagerFactory entityManagerFactory;
...
EntityManager entityManager = entityManagerFactory.createEntityManager();
EntityTransaction entityTransaction = entityManager.getTransaction();
 entityTransaction.begin();
 entityManager.persist(entity);
entityTransaction.commit();
entityManager.close();

 On accède manuellement au gestionnaire de transactions JTA et on ajoute des informations de délimitation de transaction lors de l'exécution d'opérations avec des beans Entity. L'interface "javax.transaction.UserTransaction" définit des méthodes pour commencer, valider et renverser des transactions. On injecte une instance de UserTransaction en créant une variable d'instance annotée avec @Resource:

@Resource
UserTransaction utx;

Pour démarrer une transaction, on fait appel à la méthode "UserTransaction.begin". Lorsque toutes les opérations concernant le bean Entity sont terminées, on appelle la méthode "UserTransaction.commit" pour valider la transaction. La méthode "UserTransaction.rollback" est utilisée pour annuler la transaction en cours.
L'exemple suivant montre comment gérer les transactions dans une application qui utilise un gestionnaire de beans Entity gérés par l'application:

@PersistenceUnit
EntityManagerFactory emf;
@Resource
UserTransaction utx;
...
EntityManager em = emf.createEntityManager();
try {
  utx.begin();
  em.persist(SomeEntity);
  em.merge(AnotherEntity);
  em.remove(ThirdEntity);
  utx.commit();
 } catch (Exception e) {
  utx.rollback();
}

(à suivre)...

3. Unité de persistance

Une unité de persistance définit l'ensemble de tous les beans Entity qui sont gérés par des instances d'EntityManager dans une application. Cet ensemble de beans Entity représente les données contenues dans une seule base de données.
Les unités de persistance sont définies par un fichier de configuration appelé persistence.xml:

<persistence>
   <persistence-unit name="OrderManagement">
   <description>This unit manages orders and customers.
       It does not rely on any vendor-specific features and can
       therefore be deployed to any persistence provider.
   </description>
   <jta-data-source>jdbc/MyOrderDB</jta-data-source>
    <jar-file>MyOrderApp.jar</jar-file>
    <class>com.widgets.Order</class>
    <class>com.widgets.Customer</class>
   </persistence-unit>
</persistence>

Ce fichier définit une unité de persistance nommée OrderManagement, qui utilise une source de données "JTA-aware: jdbc/MyOrderDB". Les éléments jar-file et class spécifient les classes de persistance gérées: classes d'entités, classes incorporables et superclasses mappées. L'élément jar-file spécifie les fichiers JAR qui sont visibles pour l'unité de persistance empaquetée qui contiennent des classes de persistance gérées, tandis que l'élément de classe nomme explicitement les classes de persistance gérées.
Les éléments jta-data-source (pour les sources de données JTA) et non-jta-data-source (pour les sources de données non JTA) spécifient le nom JNDI global de la source de données à utiliser par le conteneur.
Le fichier JAR ou le répertoire dont le répertoire META-INF contient persistence.xml s'appelle la racine de l'unité de persistance. La portée de l'unité de persistance est déterminée par la racine de l'unité de persistance. Chaque unité de persistance doit être identifiée avec un nom qui est unique à la portée de l'unité de persistance.
Les unités persistantes peuvent être empaquetées dans un fichier WAR ou EJB JAR ou peuvent être emballées comme un fichier JAR qui peut ensuite être inclus dans un fichier WAR ou EAR:

  • Si on colle l'unité persistante comme un ensemble de classes dans un fichier JAR EJB, le fichier "persistence.xml" doit être placé dans le répertoire "META-INF" du JAR du EJB.
  • Si on classe l'unité de persistance comme un ensemble de classes dans un fichier WAR, le fichier "persistence.xml" doit se trouver dans le répertoire "WEB-INF/classes/META-INF" du fichier WAR.
  • Si on colle l'unité de persistance dans un fichier JAR qui sera inclus dans un fichier WAR ou EAR, le fichier JAR doit être situé dans:
    • Le répertoire "WEB-INF/lib" d'un war
    • Le répertoire de la bibliothèque du fichier EAR.

(à suivre)...

4. Annotation d'une bean Entity

4.1. Principales annotations

Les principales annotations d'une classe JPA Entity sont @Entity, @Table et @Access. L'annotation @Entity nous indique que cette classe est une classe persistante. Elle peut prendre un attribut name, qui fixe le nom de ce bean Entity. Par défaut, le nom d'un bean Entity est le nom complet de sa classe. On peut fixer le nom d'un bean Entity par une constante, en utilisant un import statique.

L'annotation @Table permet de fixer le nom de la table dans laquelle les instances de cette classe vont être écrites. Cette annotation est particulièrement utile lorsque l'on doit associer un jeu de classes à des tables existantes. L'annotation @Table supporte plusieurs attributs:  

  • Les attributs catalog, schema et name : permettent de fixer les paramètres de la table utilisée.
  • L'attribut @UniqueConstraints permet d'écrire des contraintes d'unicité sur des colonnes ou des groupes de colonnes.

Enfin, l'annotation @Access permet de fixer la façon dont l'EntityManager va lire et écrire les valeurs des champs de cette classe. Cette annotation ne prend qu'un seul attribut, qui ne peut prendre que deux valeurs : AccessType.FIELD et AccessType.PROPERTY. Dans le premier cas, les lectures/modifications se font directement sur les champs, dans le second elles passent par les getters/setters . Notons que cette annotation peut aussi être placée sur les champs ou getters d'une classe. On peut donc particulariser l'injection de valeur champ par champ.

(à suivre)...

précédent suivant