Introduction Gestion de la correspondance objet/relationnel ORM: Object Relationnel Mapping Framework: Hibernate, Tooplik,… JPA: Java persistance API Mapping Objet Relationnel Avec JPA Les entités Annotations Fichier de configuration Entity Manager Quelques Exemples Gestion des relations
Architecture multicouches La couche [3], appelée [dao] (Data Access object) est la couche qui fournit à la couche [2] des données pré- enregistrées (fichiers, bases de données,...) et qui enregistre certains des résultats fournis par la couche [2].
La couche DAO gère des données provenant de deux mondes: Monde relationnel (BD) Monde Objet (Couche metier) Comment gérer la persistance des objets provenant de la couche métier? Il faut faire la correspondance entre les deux mondes. Solutions: Manuelle (solution 1) Utilisation d’un framework de mapping R\O (solution 2)
30% du code java est consacré à gérer la mise en rapport SQL/JDBC et la non correspondance entre le modèle objet et le modèle relationnel Gestion des associations Correspondances de types Ex: Personne p; SQL/JDBC: String req= ’’insert into personne values (’’+id+’’,’ ’’+nom +’’ ’,’’ ’+ prenom +’’ ’ ) ’’;
Le mappage Objet/Relationnel (ORM) désigne la persistance automatisée et transparente d’objets dans une application Java vers les tables d’une base de données relationnelle à l’aide des méta données décrivant le mapping entre les objets et la base de données. Les outils et frameworks ORM réduisent grandement le code qui permet cette correspondance et facilitent donc les adaptations mutuelles des deux modèles. Exemple de frameworks: Hibernate Toplink,…
C’est une technique de mapping d’une représentation des données d’un modèle objet vers un modèle relationnel de base de données et inversement. Quatre éléments constituant une solution ORM : 1. Une API pour effectuer les opérations CRUD (Create, Read,Update, delete) de base sur les objets des classes persistantes. 2. Un langage ou une API pour spécifier des requêtes qui se réfèrent aux classes et aux propriétés des classes. 3. Un système pour spécifier des métadonnées de mapping. 4. Une technique pour l’implémentation du mapping objet/relationnel permettant d’interagir avec des objets
Hibernate est un ORM (Object Relational Mapping) qui fait le pont entre le monde relationnel des bases de données et celui des objets manipulés par Java. Le développeur de la couche [dao] ne voit plus la couche [Jdbc] ni les tables de la base de données. Il ne voit que l'image objet de la base de données, image objet fournie par la couche [Hibernate]. XML constitue le format de description de la correspondance entre les tables relationnelles et les classes Java.
Hibernate fournit au développeur, un langage HQL (Hibernate Query Language) pour interroger le contexte de persistance [4] et non la BD elle-même. Limites: Hibernate est populaire mais complexe à maîtriser: Dès qu'on a une base de données avec des tables ayant des relations un-à-plusieurs ou plusieurs-à-plusieurs, la configuration du pont relationnel / objets n'est pas à la portée du premier débutant venu. Des erreurs de configuration peuvent alors conduire à des applications peu performantes. Apparition des plusieurs ORMs Toplink JDO
Devant le succès des produits ORM, Sun le créateur de Java, a décidé de standardiser une couche ORM via une spécification appelée JPA apparue en même temps que Java 5: Avec JPA, l'architecture précédente devient la suivante :
Avantages: Avant, la couche [dao] est très dépendante du Framework ORM choisi. Si le développeur décide de changer sa couche ORM, il doit également changer sa couche [dao] qui avait été écrite pour dialoguer avec un ORM spécifique Maintenant, il va écrire une couche [dao] qui va dialoguer avec une couche JPA. Quelque soit le produit qui implémente celle-ci, l'interface de la couche JPA présentée à la couche [dao] reste la même
La communication entre les mondes objet et relationnel suppose une transformation pour adapter la structure des données relationnelles au modèle objet. Les Entités Les classes dont les instances peuvent être persistantes sont appelées des entités dans la spécification de JPA Le développeur indique qu’une classe est une entité en lui associant
Considérons une base de données ayant une unique table [personne] dont le rôle est de mémoriser quelques informations sur des individus : La table Personne contient les attributs suivants: ID: clé primaire de la table VERSION: version de la ligne dans la table. A chaque fois que la personne est modifiée, son n° de version est incrémenté. NOM : nom de la personne PRENOM: son prénom DATENAISSANCE: sa date de naissance MARIE: entier 0 (non marié) ou 1 (marié) NBENFANTS: nombre d'enfants de la personne
L'objet [Personne] image de la table [personne] présentée précédemment pourrait être le suivant :
La configuration se fait à l'aide d'annotations Les annotations Java sont soit exploitées par le compilateur, soit par des outils spécialisés au moment de l'exécution. Remarques: En l'absence des outils capables de les interpréter, ces annotations sont ignorées. La classe [Personne] ci-dessus pourrait être exploitée dans un contexte hors JPA.
Page 16 PICSALARYID " EMP") public class Employee { private int id; private String name; private double salary; private byte[] pic; // getters & @Column(name="EMP_NAME") « BLOB »
est la première annotation indispensable. Elle se place avant la ligne qui déclare la classe et indique que la classe en question doit être gérée par la couche de persistance JPA. En l'absence de cette annotation, toutes les autres annotations JPA seraient ignorées. désigne la table de la base de données dont la classe est une représentation. Son principal argument est name qui désigne le nom de la table. En l'absence de cet argument, la table portera le nom de la classe sert à désigner le champ dans la classe qui est image de la clé primaire de la table. Cette annotation est obligatoire.
sert à faire le lien entre un champ de la classe et la colonne de la table dont le champ est l'image. L'attribut name indique le nom de la colonne dans la table. En l'absence de cet attribut, la colonne porte le même nom que le champ. L'argument nullable=false indique que la colonne associée au champ ne peut avoir la valeur NULL et que donc le champ doit avoir nécessairement une valeur. indique comment est générée la clé primaire lorsqu'elle est générée automatiquement par le SGBD. désigne le champ qui sert à gérer les accès concurrents à une même ligne de la table.
Un seul fichier de configuration XML IL est nécessaire d’indiquer au fournisseur de persistance comment il peut se connecter à la base de données Ce fichier peut aussi comporter d’autres informations
On appelle " contexte de persistance " l'ensemble des objets gérés par la couche JPA dans le cadre de ce pont objet / relationnel. Pour accéder aux données du contexte de persistance, un client JPA [1] doit passer par la couche JPA [2] : il peut créer un objet et demander à la couche JPA de le rendre persistant. L'objet fait alors partie du contexte de persistance. il peut demander à la couche [JPA] une référence d'un objet persistant existant. il peut modifier un objet persistant obtenu de la couche JPA. il peut demander à la couche JPA de supprimer un objet du contexte de persistance.
La couche JPA présente au client une interface appelée [EntityManager] qui, comme son nom l'indique permet de gérer les du contexte de persistance.
La classe Persistence permet d’obtenir une fabrique de gestionnaire d’entités par la méthode createEntityManagerFactory EX: EntityManagerFactory emf ; emf=Persistence.createEntityManagerFactory("expojpa"); EntityManagerFactory capable de fournir des objets EntityManager destinés à gérer des contextes de persistance liés à l'unité de persistance nommée expojpa. EntityManager em = emf.createEntityManager();
void flush() Toutes les modifications effectuées sur les entités du contexte de persistance gérées par l’EntityManager sont enregistrées dans la BD lors d’un flush Au moment du flush, l’EntityMnager étudie ce qu’il doit faire pour chacune des entités qu’il gère et il lance les commandes SQL adaptées pour modifier la base de données (INSERT, UPDATE ou DELETE) void persist(Object entité) Une entité nouvelle devient une entité gérée, l’état de l’entité sera sauvegardé dans la BD au prochain flush ou commit void remove(Object entité) Une entité gérée devient supprimée, les données correspondantes seront supprimées de la BD
void lock(Object entité, LockModeType lockMode) Le fournisseur de persistance gère les accès concurrents aux données de la BD représentées par les entités avec une stratégie optimiste, lock permet de modifier la manière de gérer les accès concurrents à une entité void refresh(Object entité) L’EntityManager peut synchroniser avec la BD une entité qu’il gère en rafraichissant son état en mémoire avec les données actuellement dans la BD. Utiliser cette méthode pour s’assurer que l’entité a les mêmes données que la BD
JPQL (Java Persistence Query Language) est un langage pour requêter le contexte de persistance. Q1: Sélectionner tous les éléments de la table associée à [Personne] et les rendait par ordre croissant du nom: JPQL QUERY: select p from Personne p order by p.nom asc
Exemple d’un programmer qui affiche la liste des personne sur la console private static EntityManagerFactory emf = Persistence.createEntityManagerFactory("jpa"); private static EntityManager em = emf.createEntityManager(); System.out.println("[personnes]"); for (Object p : em.createQuery("select p from Personne p order by p.nom asc").getResultList()) { System.out.println(p); }
Q2: Sélectionner une personne connaissant son ID: Deux solutions: Utiliser la méthode Find de la classe EntityManager Personne p = em.find(Personne.class, 10); Ajouter la clause where à la requête précédente Personne p1 = (Personne) em.createQuery("select p from Personne where p.id=:Id").setParameter("Id", 10).getSingleResult();
Crée deux personnes et les place dans le contexte de persistance: p1 = new Personne("Martin", "Paul", new SimpleDateFormat("dd/MM/yy").parse("31/01/2000"), true, 2); p2 = new Personne("Durant", "Sylvie", new SimpleDateFormat("dd/MM/yy").parse("05/07/2001"), false, 0); // début transaction EntityTransaction tx = em.getTransaction(); tx.begin(); // persistance des personnes em.persist(p1); em.persist(p2); // fin transaction tx.commit();
Changer le nombre d’enfant du martin (2 par 3) p1.setNbEnfant(); tx.begin(); em.merge(p1); tx.commit(); Supprimer martin: tx.begin(); em.remove(p1); tx.commit();
Page 31 4 types de relations à définir entre les entités de la JPA: One to One Many to One One to Many Many to Many
Page public class Employee { private int id; private Department d; // getters & public class Department { private int id; private String dname; // getters & @ManyToOne FKPK DEPT_IDID EMP PK DNAMEID
Page public class Employee { private int id; private Department d; // getters & public class Department { private int id; private String dname; private Collection emps; // getters & @ManyToOne FKPK DEPT_IDID EMP PK
Page public class Employee { private int id; private ParkingSpace space; // getters & public class ParkingSpace { private int id; private int lot; private String location; private Employee emp; // getters & @OneToOne FKPK P_SPACEID EMP PK
2006 Communication interactive et nouvelles technologies / Interactive Communications through New Technologies Page public class Employee { private int id; private Collection public class Project { private int id; private String name; private Collection e; // getters & @ManyToMany PK SALARYNAMEID EMP PK NAMEID PK,FK1 PROJ_IDEMP_ID EMP_PROJ
4 types de relations à définir entre les entités de la JPA: One to One (un à un) Many to One One to Many Many to Many
= "jpa03_hb_personne") public class Personne implements Serializable{ // = = "adresse_id", unique = true, nullable = false) private Adresse adresse; } = "jpa03_hb_adresse") public class Adresse implements Serializable { // = "adresse " ) private Personne personne; }
désigne une relation un-à-un Une personne a au moins et au plus une adresse. L'attribut cascade = CascadeType.ALL signifie que toute opération (persist, merge, remove) sur [Personne] doit être cascadée sur [Adresse]. Si p est une personne et a son adresse : Une opération em.persist(p) explicite entraînera une opération em.persist(a) implicite Une opération em.merge(p) explicite entraînera une opération em.merge(a) implicite Une opération em.remove(p) explicite entraînera une opération em.remove(a) implicite
définit la clé étrangère que possède la table de [Personne] sur la table de [Adresse]. L'attribut name définit le nom de la colonne qui sert de clé étrangère. L'attribut unique=true force la relation un-à-un : on ne peut avoir deux fois la même valeur dans la colonne [adresse_id]. L'attribut nullable=false force une personne à avoir une adresse.
public class Article implements Serializable private Long private int = 30) private = "categorie_id", nullable = false) private Categorie public class Categorie implements Serializable private Long private int = 30) private String = "categorie", cascade = { CascadeType.ALL }) private Set articles = new HashSet (); On a une relation un-à-plusieurs (Categorie -> Article) et la relation inverse plusieurs-à-un (Article -> Categorie). Cette relation est matérialisée par la clé étrangère que possède la table [article] sur la table [categorie]
désigne une relation un-à-plusieurs. Le One désigne [Categorie] dans laquelle on se trouve, le Many le type [Article] Une (One) catégorie à plusieurs (Many) articles. L'annotation est l'inverse (mappedBy) de l'annotation ManyToOne placée sur le champ categorie de Article : mappedBy=categorie. Remarques: La relation ManyToOne placée sur le champ categorie de Article est la relation principale. Elle est indispensable. Elle matérialise la relation de clé étrangère qui lie Article à Categorie. La relation OneToMany placée sur le champ articles de Categorie est la relation inverse. Elle n'est pas indispensable. C'est une commodité pour obtenir les articles d'une catégorie. Sans cette commodité, ces articles seraient obtenus par une requête JPQL.
Les tables précédentes vont être représentées par suivantes : Personne représentera la table [personne] Adresse représentera la table [adresse] Activite représentera la table [activite] PersonneActivite représentera la table [personne_activite]
une relation un-à-un relie l'entité Personne à l'entité Adresse : une personne p a une adresse a. une relation plusieurs-à-plusieurs relie les entités Personne et Activite : une personne a plusieurs activités et une activité est pratiquée par plusieurs personnes.
Cette relation pourrait être réalisée au moyen de deux relations un-à-plusieurs (OneToMany): Une relation un-à-plusieurs qui relie l'entité Personne à l'entité PersonneActivite. une ligne (One) de la table [personne] est référencée par plusieurs (Many) lignes de la table [personne_activite]. La table [personne_activite] détenant la clé étrangère détiendra la principale et l'entité Personne la inverse. La même chose pour l’entité Activité.
= "jpa07_hb_personne") public class Personne implements Serializable { = = "adresse_id", unique = true, nullable = false) private Adresse adresse; // relation Personne (one) -> PersonneActivite (many) // inverse de la relation existante PersonneActivite (many) -> Personne (one) // cascade suppression Personne -> supression = "personne", cascade = { CascadeType.REMOVE }) 45. private Set activites = new HashSet ();
@Entity = "jpa07_hb_activite") public class Activite implements Serializable { //champs // cascade suppression Activite -> supression = "activite", cascade = { CascadeType.REMOVE }) private Set personnes = new HashSet ();
@Entity // table de = "jpa07_hb_personne_activite") public class PersonneActivite public static class Id implements Serializable { // composantes de la clé composite // pointe sur une = "PERSONNE_ID") private Long personneId; // pointe sur une = "ACTIVITE_ID") private Long private Id id = = "PERSONNE_ID", insertable = false, updatable = false) private = "ACTIVITE_ID", insertable = false, updatable = false) private Activite activite; }
Merci pour votre attention