JPA

JPA (Java Persistance Api) est une spécification Java EE permettant de lier des objets à des données contenues dans une base de données. Plus exactement, dans toute source de données possédant une implémentation correspondante.

JPA est essentiellement utilisé pour la manipulation des bases de données relationnelles construites à partir du développement orienté objet en Java. Comme tout ORM, JPA fournit une abstraction d'accès à la base de données. Il n'est donc plus question d'écrire du code SQL "natif" de votre base. Les opérations de création, recherche, mise à jour et suppression des données sont complètement accessibles via des méthodes génériques.

En fonction de la persistance choisie, l'implémentation JPA pour votre base se chargera de traduire ces appels en SQL (ou autre) valide.

La vidéo suivante est une présentation générale de JPA.

Transparents présentés dans la vidéo

La suite de cette page explique comment mettre en oeuvre JPA à l'aide de l'implémentation Hibernate.

Hibernate

Essayons de mettre en oeuvre une persistance JPA avec l'implémentation Hibernate par dessus une base de données "H2".

Hibernate est un "gros" framework qui implémente les fonctionnalités de JPA. Il possède un nombre assez important de dépendances. Il devient assez vite compliqué de vouloir chercher toutes ces dépendances "à la main". Nous allons plutôt utiliser Maven pour gérer tout cela.

Premières entités

Avec JPA, nous pouvons annoter nos classes Java pour indiquer que les données de ces classes seront persistées. De manière simple, il "suffit" de placer l'annotation @Entity sur une classe pour que cela fonctionne ... ou presque. Il est nécessaire que les propriétés de l'objet à persister soient accessibles par le gestionnaire d'entité.

Comprenez :
- soit une propriété public ;
- soit un getter / setter avec le nom qui correspond getPropriété et setPropriété( valeur ).
- sinon l'implémentation JPA (hibernate) pourra essayer de modifier dynamiquement le bytecode pour rendre ces propriétés accessibles.

Pour certaines propriétés, il est utile de mettre d'autres annotations pour indiquer comment le gestionnaire ou la base se comportera. Par exemple l'identifiant de l'entité doit être annoté @Id. Nous pouvons aussi indiquer que cette propriété sera générée par la base via une séquence (ou auto increment). Pour cela nous ajoutons la propriété @GeneratedValue

Les associations

Pour marquer les relations entre nos entités il est possible d'utiliser les annotations :
» @OneToMany : pour désigner une relation 1-n, soit une instance faisant référence à plusieurs autres. Permet d'accéder aux objets du coté N à travers une liste. A utiliser avec vigilance selon la volumétrie des données associées.
» @ManyToOne : pour désigner, une relation inverse de 1-n. Permet d'accéder facilement à l'objet du coté "1".
» @OneToOne : pour désigner une relation 1-1 entre deux objets. Peut être utile pour séparer un ensemble de propriétés dont le sens est différent. Peut être utile pour concevoir une relation d'héritage également.
» @ManyToMany : pour les relations n-n devenant une table associative.

Certaines associations N-N du modèle peuvent comporter des informations. Dans ce cas, il est préférable d'utiliser deux liens OneToMany vers une classe qui portera ces informations.
Pour gérer l'association correctement, il y a ensuite deux stratégies concernant la clé primaire :
1. Réaliser une clé composée à l'aide de EmbeddedId ; (Voir cette explication)
2. Ajouter une simple clé (colonne supplémentaire) et mettre une contrainte d'unicité sur les colonnes des deux clé étrangères. ; (Voir cette explication)

persistence.xml

Si vous utilisez JPA sans autre framework (type Spring), le "contexte de persistance" se configure avec un fichier "persistence.xml" placé dans le classpath, dans un répertoire "META-INF/". Dans un projet Maven, ce fichier est généralement placé dans les "resources" principales. Il faut créer un répertoire WEB-INF/resources/META-INF et y mettre ce contenu :.

<?xml version="1.0" encoding="UTF-8"?>
<persistence xmlns="http://xmlns.jcp.org/xml/ns/persistence"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence
             http://xmlns.jcp.org/xml/ns/persistence/persistence_2_1.xsd"
             version="2.1">
<persistence-unit name="myApp">
<provider>org.hibernate.jpa.HibernatePersistenceProvider</provider>
<properties>
  <property name="javax.persistence.jdbc.driver"   value="org.h2.Driver"/>
  <property name="javax.persistence.jdbc.url"      value="jdbc:h2:./db"/>
  <property name="javax.persistence.jdbc.show_sql" value="true"/>
  <property name="hibernate.dialect"       value="org.hibernate.dialect.H2Dialect"/>
  <property name="hibernate.hbm2ddl.auto"  value="create-drop"/>
</properties>
</persistence-unit>
</persistence>

Ce fichier est évidemment à adapter selon vos besoins.
Prêtez notamment attention au paramètre "hbm2ddl.auto" et lisez cette page de stackOverflow.

Entity Manager

L'Entity Manager permet de sauvegarder (persist), charger (find), ou créer des requêtes exprimées en JPQL ... une sorte de SQL orienté objet. Notez qu'il est aussi possible de créer des requêtes à l'aide de l'API Criteria de JPA. Pour tout cela, il y a la doc. Consultez par exemple ce site ou encore LA doc officielle, un peu moins agréable.
Vous pouvez aussi vous plonger dans la javadoc, encore plus austère.

Vous pouvez consulter la classe DemoJPA pour voir un exemple simple d'usage de l'entity manager et des transactions. Toutes les requêtes JPA doivent être exécutées dans une transaction. On "oblige" le développeur à se soucier de ce qui a du sens en terme d'état de base de données. Si une exception survient durant la transaction, les modifications en base ne sont pas appliquées (ou défaites).

Le screencast illustre l'utilisation de l'Entity Manager de JPA.
Dans ce projet les dépendances sont gérées par Maven.

Code présenté dans le screencast

Bien que ce ne soit pas recommandé, il reste possible d'effectuer des requête SQL à travers l'API JPA.
Le mapping peut rester "automatique" si toutefois votre résultat de requête reste compatible avec l'objet à affecter.

  String sql = "SELECT * FROM USER WHERE ID = ?";

  Query query = entityManager.createNativeQuery(sql, User.class);
  query.setParameter(1, id);
  User user = (User) query.getSingleResult();

Les transactions JPA

JPA impose l'usage de transaction dès lors que vous souhaitez modifier les données.

Une transaction est un mécanisme qui permet d'exécuter de façon sûre une modification sur une base de données : soit la modification est effectuée complètement, soit elle n'est pas effectuée du tout. On obtient ainsi la garantie qu'une modification ne sera jamais exécutée partiellement, ce qui risquerait de laisser les données de la base dans un état incohérent.

Une transaction apporte également des propriétés clés dès lors que plusieurs utilisateurs accèdent aux mêmes données simultanément. On obtient la garantie que les modifications faites par un utilisateur ne perturbent pas le fonctionnement des autres utilisateurs.

L'exemple "Hello World!" qui illustre l'utilisation des transactions est le transfert d'une somme d'argent entre deux comptes bancaires : il faut débiter un compte et en créditer un autre. Soit le transfert est effectué complètement, soit il ne doit pas l'être du tout. Il ne s'agit en aucune sorte que de l'argent disparaisse en débitant un compte sans créditer l'autre. Les transactions permettent d'obtenir une telle garantie.

Concrètement, les transactions sont mises en oeuvre à l'aide de trois opérations :

La vidéo suivante présente l'utilisation des transactions avec JPA.

Transparents présentés dans la vidéo

JPQL

JPA propose un language de requêtage proche de SQL afin de manipuler les données. Le point de vue est orienté "entité" et les opérations de filtrage ou de jointure sont décrites en fonction des attributs.
Ce language permet de s'abstraire du modèle relationnel et des subtilités de différence entre le SQL implémenté par chaque gestionaire de base de données.

Le screencast suivant montre l'usage d'opérations courantes sur les entités à l'aide de JPQL.

Code présenté dans le screencast

API Criteria

L'API Criteria de JPA offre la possibilité de construire les requêtes de manière programmatique, c'est-à-dire en utilisant des objets et des méthodes Java. C'est une alternative à JPQL pour les requêtes de type SELECT. Alors qu'en JPQL (comme en JDBC) les requêtes sont définies avec des chaînes de caractères, les requêtes avec l'API Criteria sont définies via l'instantiation d'objets Java qui représentent les éléments de la requête. Cela donne un code plus fortement typé qui permet d'éviter les erreurs de syntaxe inhérentes à la manipulation de chaînes de caractères.

Comme toute API, Criteria propose au développeur un ensemble de méthodes. En plus de cela, Criteria génère des métadonnées pour les entités de l'application. Chaque donnée d'une entité est ainsi associée à un métaparamètre qui est généré automatiquement par Criteria. Le développeur peut alors utiliser ces métaparamètres pour construire des requêtes fortement typées.

La vidéo suivante présente l'API Criteria.

Transparents présentés dans la vidéo

Le screencast suivant reprend les entités du screencast JPQL pour effectuer un ensemble d'opérations similaires à l'aide de l'API Criteria.

Code présenté dans le screencast

Héritage et persistance

Les entités JPA sont représentés sous la forme de classe Java. Il devient alors possible de créer un arbre d'héritage entre nos entités. Mais comment ces données sont-elles persitées dans une base de données relationnelles ?
La vidéo suivante vous détaille les stratégies de persistance proposées par JPA.

Transparents présentés dans la vidéo

Exercice

Nous vous proposons un exercice qui permettra de vous familiariser par la pratique avec JPA.