Cours "Modèles pour la persistance des objets"

Java Persistence API

Home (Contact)

Objectif du TP :

Présenter le standard JPA pour la persistance des objets en Java.

Support de cours, partie 1 Support de cours, partie 2


L'API pour la persistance des objets Java (JPA 2.0) permet de rendre persistant des objets Java sans avoir besoin d'un serveur d'application. La spécification de JPA 2.0 est sortie à la fin de l'année 2009. Ce TP est en cours de mise à jour pour s'adapter à cette nouvelle spécification. Plusieurs implémentations de cette spécification sont en cours de développement. Ce TP utilise l'implémentation de référence EclipseLink.

Il est impossible en un seul TP de voir toutes les possibilités offertes par JPA. En particulier, ce TP n'aborde pas les entités détachées qui sont pourtant importantes dans la pratique lorsque l'application est distribuée sur plusieurs couches distantes.


Installer l'API

Pour gagner du temps, installez cet environnement avant le début du TP.

Allez à l'adresse http://www.eclipse.org/eclipselink/downloads/ et téléchargez la dernière version (2.1.1, datée du 26 août 2010, au moment où ces lignes sont écrites). Il s'agit d'un fichier zip dont le lien est de la forme "EclipseLink 2.1.1 Installer Zip (28 MB)" (et le fichier de la forme eclipselink-2.x.x.vxxxxxxxx-rxxx.zip ; x et xxxxxx sont des numéros de version). Décompactez ce fichier zip quelque part où vous mettez vos librairies Java. Le fichier jar qu'il vous faudra mettre dans votre classpath est le fichier jlib/eclipselink.jar. Si le paquetage javax.persistence n'est pas reconnu, il faudra aussi mettre dans le classpath le fichier jar qui contient JPA 2.0 ; il est dans le répertoire jlib/jpa et il se nomme javax.persistence_2.0.x.vxxxxxx.jar. Si vous avez des difficultés pour récupérer les fichiers, vous pouvez récupérer les fichiers de la version 2.1.1 ici et . Habituellement les jars sont placés dans le répertoire lib du projet, au même niveau que les répertoires src et classes.

Cette implémentation fonctionne avec n'importe quel SGBD (à vérifier...) et pas seulement avec Oracle.

Il faudra aussi ajouter le driver Oracle à votre projet (le même que pour les TPs sur JDBC).

Javadoc API JPA

Spécification JPA 2.0


Modèle des données

Vous allez écrire les classes Java qui correspondent à la base de données des employés d'une entreprise, utilisée depuis le début du cours sur les bases de données.

Classes :

Dans le deuxième exercice, pour illustrer les problèmes liés à l'héritage, vous ajouterez une classe Personne (avec un champ nom et un identificateur non significatif) dont hérite la classe Employe, et une classe Client (avec seulement une adresse pour simplifier) qui hérite aussi de Personne. La classe Adresse ne sera pas une entité mais une classe insérée (Embedded).

Associations :

Pour vous faire gagner du temps, des squelettes de classes vous sont fournis dans un fichier zip. Il vous faudra placer les classes au bon endroit et y ajouter des annotations ou même éventuellement d'autres informations (l'association entre les employés et les projets n'a pas été implémentée). Dans ce zip vous trouverez aussi un fichier persistence.xml que vous devrez placer au bon endroit (dans un répertoire META-INF, dans le classpath) et modifier pour indiquer les bonnes informations pour la connexion à la base de données et pour ajouter des noms de classes entités. Des propriétés propres à GlassFish ont été positionnées pour faire afficher des informations sur le déroulement de l'exécution de JPA, en particulier les ordres SQL générés (lisez la console pendant l'exécution du programme) et pour recréer les tables à chaque exécution (pratique pour tester mais évidemment à enlever en production) ; vous pourrez enlever des propriétés à partir d'un certain moment si vous voulez conserver les données déjà dans la base de données.

Dans la suite vous vous arrangerez pour ne pas interférer avec les tables qui pourraient déjà exister dans votre base de données. Rappel : les tables utilisées pendant les premiers TPs s'appelaient EMP, DEPT, PROJET, PARTICIPATION. Vous ferez générer les tables PROJET2, PARTICIPATION2 (remarquez le "2" final pour les 2 dernières tables) au lieu de PROJET et de PARTICIPATION. Si vous ne savez pas comment faire, demandez à l'enseignant qui encadre votre TP. Attention, si vous n'y prenez garde, vos anciennes tables PROJET et PARTICIPATION seront écrasées ! Toutes les autres tables auront les valeurs par défaut prévues par JPA pour les noms de tables ; par exemple, la table associée à la classe Departement s'appellera DEPARTEMENT (la casse n'a pas d'importance pour les noms de tables en SQL ; pour les différencier des classes, on mettra dans cet énoncé les noms des tables en majuscules).


Pour se chauffer...

Juste pour tester, vous allez travailler avec uniquement les départements, sans les autres classes. Dans la classe Departement, mettez en commentaires tout ce qui est lié à l'association avec Employe.

  1. Complétez la classe Departement pour la transformer en entité JPA. Les numéros de département devront être générés automatiquement.
  2. La méthode main de la classe Test1 crée 3 départements dont seulement 2 seront rangés dans la base de données. Le lieu de l'emplacement d'un des départements est modifié après sa création et après l'appel de persist et pourtant cette modification est bien enregistrée dans la base (vérifiez-le). Si vous ne savez pas pourquoi, demandez à l'enseignant qui encadre votre TP car ce fait est lié à une notion importante.
  3. A la fin de l'exécution de cette méthode main, la base de données devra contenir les données associées aux départements ajoutés dans la méthode main. Vérifiez-le. Remarquez aussi que le département de Nantes n'a pas été enregistré dans la base de données.
  4. Étudiez ce qui est affiché lors de l'exécution de la classe, en particulier les définitions des tables créées par JPA. Vous étudierez ces définitions à chaque fois que vous modifierez votre modèle objet dans les prochains exercices pour voir comment JPA effectue le mapping objet-relationnel.

Correction :

Les classes


Héritage et association 1:N

Enlevez les commentaires que vous avez mis dans la question précédente dans la classe Departement pour tout ce qui est lié à l'association avec la classe Employe. Complétez les classes Personne, Employe et Client. Vous les utiliserez avec cette classe Test2.

Utilisez la stratégie "une seule table par arborescence d'héritage".

A la fin de l'exécution de cette méthode main, la base de données devra contenir les bonnes informations. Vérifiez-le.

Correction

Définition de l'unité de persistance
Les classes


Langage d'interrogation de JPA

Commencez par modifier le fichier persistence.xml pour ne pas écraser les tables existantes : remplacez drop-and-create par create.

  1. Cette fois-ci vous allez écrire une classe Test3 qui va récupérer tous les employés du département "DIRECTION" (supposez que le nom du département est toujours donné en majuscule ; attention, le nom dans la base n'est pas toujours en majuscule) et qui affiche ensuite leur nom. Le nom du département devra être un paramètre de la requête. La réponse ne devra pas tenir compte de la casse dans le nom du département. Dans cette question et la suivante vous utiliserez des requêtes dynamiques.
  2. Ecrivez une 2ème version du programme qui ne récupère pas les employés mais seulement leur nom et leur salaire dans la base de données et qui les affiche.
  3. Ecrivez une 3ème version qui affiche encore les noms des employés, en utilisant une requête nommée.
  4. Utilisez la requête de la première version pour augmenter de 5 % le salaire des employés récupérés et pour enregistrer les modifications dans la base.
  5. Mettez tous les salaires des employés à 2200 euros par une modification "en volume" (bulk update). Vérifiez que cette opération n'a pas modifié les entités en mémoire. A cause de ce fait, il ne faut pas lancer une modification en volume si le contexte de persistance contient déjà des entités. Il est tout de même possible de synchroniser une entité en mémoire avec les valeurs de la base de données. Comment faire ? Testez.

Correction

Les classes


Association M:N

Complétez la classe Projet écrivez une classe Test4 dont la méthode main crée 3 employés, 2 projets et répartit les 3 employés dans ces 2 projets. Attention, c'est pour cet exercice qu'il faut penser à ne pas écraser vos tables PROJET et PARTICIPATION.

Voici la méthode de la classe Projet qui servira à ajouter un employé dans un projet :
void ajouterParticipant(Employe employe, String fonction)
Le concepteur de l'application souhaite que l'entité Participation ne soit pas visible pour l'utilisateur de la classe Projet. Que devrez-vous faire pour rendre persistantes les participations sans que ces participations ne soient visibles ?

Que faudrait-il modifier si un employé pouvait avoir plusieurs fonctions dans un même projet ?

Correction

Définition de l'unité de persistance
Participation invisible
Les classes


Requêtes polymorphe (ou non). Navigation dans les requêtes

Ecrivez une classe Test5 dont la méthode main

Correction

Classe Test5


Pour ceux qui ont déjà fini...

N + 1 select

Récupérez tous les employés en dehors d'une transaction avec la requête "select e from Employe as e".

Faites afficher les noms de tous les employés.

Sans relancer une requête, en utilisant seulement les associations des objets Employe récupérés, récupérez tous les projets auxquels participent les employés. Faites afficher la liste des noms des employés ; un nom par ligne, suivi de tous les projets auxquels l'employé participe. Les noms des employés qui ne participent à aucun projet devront être affichés.

En utilisant le logging de TopLink, regardez quelles sont les requêtes SQL lancées par TopLink et à quel moment. Modifiez le mode de récupération de l'association entre les employés et les participations pour voir l'impact sur les requêtes SQL.

Comment éviter le problème des N + 1 selects ?

Correction

Classe Test6
Ordres SQL


Transactions

Il est très important de comprendre les relations qui lient les contextes de persistance et les transactions. Dans cet exercice vous allez tester quelques situations particulières.

  1. Créez un département et ajoutez-le à un contexte de persistance. Ne commencez pas de transaction et modifiez ce département (changez son lieu par exemple). Est-ce les modifications ont été effectuées dans la base de données ?
  2. A la suite du code qui crée le département et le modifie, ouvrez une transaction et refermez-la tout de suite. Est-ce que les modifications effectuées sur le département sont enregistrées dans la base de données ?
  3. Après avoir fermé la transaction, modifiez à nouveau le département et lancez un flush (sans ouvrir de transaction). Que se passe-t-il ?

Avertissement pour ceux qui vont travailler avec un serveur d'applications (avec Java EE) : par défaut les contextes de persistance seront alors limités à une transaction et le comportement ne sera pas le même que dans cet exercice où le contexte de persistance et les transactions ne sont pas gérés par le container mais par l'application.

Correction

Classe Test7


Entité versionnée

Ajoutez un attribut de version (@Version) dans l'entité Departement.

  1. Modifiez le nom d'un département et vérifiez si le numéro de version a été incrémenté.
  2. Ajoutez un employé dans un département et vérifiez si le numéro de version a été incrémenté.
  3. Testez un lock (READ) et écrivez du code Java pour obtenir une lecture répétable sur ce département. Essayez de modifier ce département dans une autre transaction en parallèle pour voir ce qui se passe (ne passez pas le département en paramètre, récupérez-le dans la 2ème transaction lancée en parallèle comme dans la première transaction). Quelle est la différence de comportement avec du code qui n'aurait pas exécuté un lock en mode READ ? Vérifiez.
  4. Utilisez un lock (WRITE) et écrivez du code Java pour faire incrémenter le numéro de version, sans modifier le département. Vérifiez en faisant afficher le numéro de version.

Correction

Classe Test9


Génération du schéma de la base de données

Recommencez le premier exercice en vous arrangeant pour que le nom du département soit limité à la longueur 25 dans la table DEPARTEMENT générée automatiquement.

Correction

Réponse


Retour