Développons en Java
v 2.40   Copyright (C) 1999-2023 .   
62. Hibernate Partie 9 : La machine virtuelle Java (JVM) Imprimer Index Index avec sommaire Télécharger le PDF

 

63. JPA (Java Persistence API)

 

chapitre    6 3

 

Niveau : niveau 4 Supérieur 

 

L'utilisation pour la persistance d'un mapping O/R permet de proposer un niveau d'abstraction plus élevé que la simple utilisation de JDBC : ce mapping permet d'assurer la transformation d'objets  vers la base de données et vice versa que cela soit pour des lectures ou des mises à jour (création, modification ou suppression).

Développée dans le cadre de la version 3.0 des EJB, cette API ne se limite pas aux EJB puisqu'elle peut aussi être mise en oeuvre dans des applications Java SE.

L'utilisation de l'API ne requiert aucune ligne de code mettant en oeuvre l'API JDBC.

L'API propose un langage d'interrogation similaire à SQL mais utilisant des objets plutôt que des entités relationnelles de la base de données.

L'API Java Persistence repose sur des entités qui sont de simples POJOs annotés et sur un gestionnaire de ces entités (EntityManager) qui propose des fonctionnalités pour les manipuler (ajout, modification suppression, recherche). Ce gestionnaire est responsable de la gestion de l'état des entités et de leur persistance dans la base de données.

Ce chapitre contient plusieurs sections :

 

63.1. L'installation de l'implémentation de référence

L'implémentation de référence est incluse dans le projet GlassFish. Elle peut être téléchargée à l'url :
https://glassfish.dev.java.net/downloads/persistence/JavaPersistence.html

Cette implémentation de référence repose sur l'outil TopLink d'Oracle dans sa version essential.

Il suffit d'exécuter la commande java -jar avec en paramètre le fichier jar téléchargé.

Exemple :
C:\>java -jar glassfish-persistence-installer-v2-b52.jar
glassfish-persistence
glassfish-persistence\README
glassfish-persistence\3RD-PARTY-LICENSE.txt
glassfish-persistence\LICENSE.txt
glassfish-persistence\toplink-essentials-agent.jar
glassfish-persistence\toplink-essentials.jar
installation complete

Lisez la licence et si vous l'acceptez, cliquez sur le bouton « Accept ».

Un répertoire glassfish-persistence est créé contenant les bibliothèques de l'implémentation de référence de JPA.

 

63.2. Les entités

Les entités dans les spécifications de l'API Java Persistence permettent d'encapsuler les données d'une occurrence d'une ou plusieurs tables. Ce sont de simples POJO (Plain Old Java Object). Un POJO est une classe Java qui n'implémente aucune interface particulière ni n'hérite d'aucune classe mère spécifique.

Un objet Java de type POJO mappé vers une table de la base de données grâce à des méta data via l'API Java Persistence est nommé bean entité (Entity bean).

Un bean entité doit obligatoirement avoir un constructeur sans argument et la classe du bean doit obligatoirement être marquée avec l'annotation @javax.persistence.Entity. Cette annotation possède un attribut optionnel nommé name qui permet de préciser le nom de l'entité dans les requêtes. Par défaut, ce nom est celui de la classe de l'entité.

En tant que POJO, le bean entity n'a pas à implémenter d'interface particulière mais il doit respecter les règles de tous Java beans :

Le bean entity est composé de propriétés qui seront mappées sur les champs de la table de la base de données sous-jacente. Chaque propriété encapsule les données d'un champ d'une table. Ces propriétés sont utilisables au travers de simples accesseurs (getter/setter).

Une propriété particulière est la clé primaire qui sert d'identifiant unique dans la base de données mais aussi dans le POJO. Elle peut être de type primitif ou de type objet. La déclaration de cette clé primaire est obligatoire.

 

63.2.1. Le mapping entre le bean entité et la table

La description du mapping entre le bean entité et la table peut être faite de deux façons :

L'API propose plusieurs annotations pour supporter un mapping O/R assez complet.

Annotation Rôle
@javax.persistence.Table Préciser le nom de la table concernée par le mapping
@javax.persistence.Column Associer un champ de la table à la propriété (à utiliser sur un getter)
@javax.persistence.Id Associer un champ de la table à la propriété en tant que clé primaire (à utiliser sur un getter)
@javax.persistence.GeneratedValue Demander la génération automatique de  la clé primaire au besoin
@javax.persistence.Basic Représenter la forme de mapping la plus simple. Cette annotation est utilisée par défaut
@javax.persistence.Transient Demander de ne pas tenir compte du champ lors du mapping

L'annotation @javax.persistence.Table permet de lier l'entité à une table de la base de données. Par défaut, l'entité est liée à la table de la base de données correspondant au nom de la classe de l'entité. Si ce nom est différent alors l'utilisation de l'annotation @Table est obligatoire. C'est notamment le cas si des conventions de nommage des entités de la base de données sont mises en place.

L'annotation @Table possède plusieurs attributs :

Attributs Rôle
name Nom de la table
catalog Catalogue de la table
schema Schéma de la table
uniqueConstraints Contraintes d'unicité sur une ou plusieurs colonnes

L'annotation @javax.persistence.Column permet d'associer un membre de l'entité à une colonne de la table. Par défaut, les champs de l'entité sont liés aux champs de la table dont les noms correspondent. Si ces noms sont différents alors l'utilisation de l'annotation @Column est obligatoire. C'est notamment le cas si des conventions de nommage des entités de la base de données sont mises en place.

L'annotation @Column possède plusieurs attributs :

Attributs Rôle
name Nom de la colonne
table Nom de la table dans le cas d'un mapping multi-table
unique Indique si la colonne est unique
nullable Indique si la colonne est nullable
insertable Indique si la colonne doit être prise en compte dans les requêtes de type insert
updatable Indique si la colonne doit être prise en compte dans les requêtes de type update
columnDefinition Précise le DDL de définition de la colonne
length Indique la taille d'une colonne de type chaîne de caractères
precision Indique la taille d'une colonne de type numérique
scale Indique la précision d'une colonne de type numérique

Hormis les attributs name et table, tous les autres attributs ne sont utilisés que par un éventuel outil du fournisseur de l'implémentation de l'API pour générer automatiquement la table dans la base de données.

Il faut obligatoirement définir une des propriétés de la classe avec l'annotation @Id pour la déclarer comme étant la clé primaire de la table.

Cette annotation peut marquer soit le champ de la classe concernée soit le getter de la propriété. L'utilisation de l'un ou l'autre précise au gestionnaire s'il doit se baser sur les champs ou les getter pour déterminer les associations entre l'entité et les champs de la table. La clé primaire peut être constituée d'une seule propriété ou composées de plusieurs propriétés qui peuvent être de type primitif ou chaîne de caractères.

La clé primaire composée d'un seul champ peut être une propriété d'un type primitif, ou une chaîne de caractères (String).

La clé primaire peut être générée automatiquement en utilisant l'annotation @javax.persistence.GeneratedValue. Cette annotation possède plusieurs attributs :

Attributs Rôle
strategy Précise le type de générateur à utiliser : TABLE, SEQUENCE, IDENTITY ou AUTO. La valeur par défaut est AUTO
generator Nom du générateur à utiliser

Exemple :
package fr.jmdoudoux.dej.jpa;

import java.io.Serializable;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;

@Entity
public class Personne implements Serializable {
  @Id
  @GeneratedValue
  private int id;

  private String prenom;

  private String nom;

  private static final long serialVersionUID = 1L;

  public Personne() {
    super();
  }

  public int getId() {
    return this.id;
  }

  public void setId(int id) {
    this.id = id;
  }

  public String getPrenom() {
    return this.prenom;
  }

  public void setPrenom(String prenom) {
    this.prenom = prenom;
  }

  public String getNom() {
    return this.nom;
  }

  public void setNom(String nom) {
    this.nom = nom;
  }

}

Le type AUTO est le plus généralement utilisé : il laisse l'implémentation générer la valeur de la clé primaire.

Le type IDENTITY utilise un type de colonne spécial de la base de données.

Le type TABLE utilise une table dédiée qui stocke les clés des tables générées. L'utilisation de cette stratégie nécessite l'utilisation de l'annotation @javax.persistence.TableGenerator

L'annotation @TableGenerator possède plusieurs attributs :

Attributs Rôle
name Nom identifiant le TableGenerator : il devra être utilisé comme valeur dans l'attribut generator de l'annotation @Id
table Nom de la table utilisée
catalog Nom du catalogue utilisé
schema Nom du schéma utilisé
pkColumnName Nom de la colonne qui précise la clé primaire à générer
valueColumnName Nom de la colonne qui contient la valeur de la clé primaire générée
pkColumnValue  
allocationSize Valeur utilisée lors de l'incrémentation de la valeur de la clé primaire
uniqueConstraints  

Le type SEQUENCE utilise un mécanisme nommé séquence proposé par certaines bases de données notamment celles d'Oracle. L'utilisation de cette stratégie nécessite l'utilisation de l'annotation @javax.persistence.SequenceGenerator

L'annotation @SequenceTableGenerator possède plusieurs attributs :

Attributs Rôle
name Nom identifiant le SequenceTableGenerator : il devra être utilisé comme valeur dans l'attribut generator de l'annotation @Id
sequenceName Nom de la séquence dans la base de données
initialValue Valeur initiale de la séquence
allocationSize Valeur utilisée lors de l'incrémentation de la valeur de la séquence

L'annotation @SequenceGenerator s'utilise sur la classe de l'entité

Exemple :
@Entity
@Table(name="PERSONNE")
@SequenceGenerator(name="PERSONNE_SEQUENCE",
sequenceName="PERSONNE_SEQ")
public class Personne implements Serializable {
  @Id
  @GeneratedValue(strategy=GenerationType.SEQUENCE, generator="PERSONNE_SEQUENCE")
  private int id;

Le modèle de base de données relationnelle permet la définition d'une clé primaire composée de plusieurs colonnes. L'API Java Persistence propose deux façons de gérer ce cas de figure :

L'annotation @IdClass s'utilise avec une classe qui va encapsuler les propriétés qui composent la clé primaire. Cette classe doit obligatoirement :

Exemple : la clé primaire est composée des champs nom et prenom (exemple théorique qui présume que deux personnes ne peuvent avoir le même nom et prénom)
package fr.jmdoudoux.dej.jpa;

public class PersonnePK implements java.io.Serializable {

  private static final long serialVersionUID = 1L;

  private String nom;

  private String prenom;

  public PersonnePK() {
  }

  public PersonnePK(String nom, String prenom) {
    this.nom = nom;
    this.prenom = prenom;
  }

  public String getNom() {
    return this.nom;
  }

  public void setNom(String nom) {
    this.nom = nom;
  }

  public String getPrenom() {
    return prenom;
  }

  public void setPrenom(String prenom) {
    this.nom = prenom;
  }

  public boolean equals(Object obj) {
    boolean resultat = false;

    if (obj == this) {
      resultat = true;
    } else {
      if (!(obj instanceof PersonnePK)) {
        resultat = false;
      } else {
        PersonnePK autre = (PersonnePK) obj;
        if (!nom.equals(autre.nom)) {
          resultat = false;
        } else {
          if (prenom != autre.prenom) {
            resultat = false;
          } else {
            resultat = true;
          }
        }
      }
    }
    return resultat;
  }

  public int hashCode() {
    return (nom + prenom).hashCode();
  }
}

Il est nécessaire de définir la classe de la clé primaire dans le fichier de configuration persistence.xml

Exemple :
<?xml version="1.0"  encoding="UTF-8"?>
<persistence  xmlns="http://java.sun.com/xml/ns/persistence"  
              xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  
              version="1.0"  xsi:schemaLocation="http://java.sun.com/xml/ns/persistence
              http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd">
    <persistence-unit  name="MaBaseDeTestPU">
         <provider>oracle.toplink.essentials.PersistenceProvider</provider>
         <class>fr.jmdoudoux.dej.jpa.Personne</class>
         <class>fr.jmdoudoux.dej.jpa.PersonnePK</class>
    </persistence-unit>
</persistence>

L'annotation @IdClass possède un seul attribut :

Attributs Rôle
Class Classe qui encapsule la clé primaire composée

Il faut utiliser l'annotation@IdClass sur la classe de l'entité.

Il est nécessaire de marquer chacune des propriétés de l'entité qui compose la clé primaire  avec l'annotation @Id. Ces propriétés doivent avoir le même nom dans l'entité et dans la classe qui encapsule la clé primaire.

Exemple :
package fr.jmdoudoux.dej.jpa;

import java.io.Serializable;

import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.IdClass;

@Entity
@IdClass(PersonnePK.class)
public class Personne implements Serializable {
  private String prenom;
  private String nom;
  private int taille;

  private static final long serialVersionUID = 1L;

  public Personne() {
    super();
  }

  @Id
  public String getPrenom() {
    return this.prenom;
  }

  public void setPrenom(String prenom) {
    this.prenom = prenom;
  }

  @Id
  public String getNom() {
    return this.nom;
  }

  public void setNom(String nom) {
    this.nom = nom;
  }

  public int getTaille() {
    return this.taille;
  }

  public void setTaille(int taille) {
    this.taille = taille;
  }
}

Remarque : il n'est pas possible de demander la génération automatique d'une clé primaire composée. Les valeurs de chacune des propriétés de la clé doivent être fournies explicitement.

La classe de la clé primaire est utilisée notamment lors des recherches.

Exemple :
PersonnePK clePersonne = new PersonnePK("nom1", "prenom1");
Personne personne = entityManager.find(Personne.class, clePersonne);

L'annotation @EmbeddedId s'utilise avec l'annotation @javax.persistence.Embeddable

Exemple :
package fr.jmdoudoux.dej.jpa;

import javax.persistence.Embeddable;

@Embeddable
public class PersonnePK implements java.io.Serializable {

  private static final long serialVersionUID = 1L;

  private String nom;

  private String prenom;

  public PersonnePK() {
  }

  public PersonnePK(String nom, String prenom) {
    this.nom = nom;
    this.prenom = prenom;
  }

  public String getNom() {
    return this.nom;
  }

  public void setNom(String nom) {
    this.nom = nom;
  }

  public String getPrenom() {
    return prenom;
  }

  public void setPrenom(String prenom) {
    this.nom = prenom;
  }

  public boolean equals(Object obj) {
    boolean resultat = false;

    if (obj == this) {
      resultat = true;
    } else {
      if (!(obj instanceof PersonnePK)) {
        resultat = false;
      } else {
        PersonnePK autre = (PersonnePK) obj;
        if (!nom.equals(autre.nom)) {
          resultat = false;
        } else {
          if (prenom != autre.prenom) {
            resultat = false;
          } else {
            resultat = true;
          }
        }
      }
    }
    return resultat;
  }

  public int hashCode() {
    return (nom + prenom).hashCode();
  }
}

Exemple :
package fr.jmdoudoux.dej.jpa;

import java.io.Serializable;

import javax.persistence.EmbeddedId;
import javax.persistence.Entity;

@Entity
public class Personne implements Serializable {
  @EmbeddedId
  private PersonnePK clePrimaire;
  private int taille;

  private static final long serialVersionUID = 1L;

  public Personne() {
    super();
  }

  public PersonnePK getClePrimaire() {
    return this.clePrimaire;
  }

  public void setNom(PersonnePK clePrimaire) {
    this.clePrimaire = clePrimaire;
  }

  public int getTaille() {
    return this.taille;
  }

  public void setTaille(int taille) {
    this.taille = taille;
  }
}

La classe qui encapsule la clé primaire est utilisée notamment dans les recherches

Exemple :
PersonnePK clePersonne = new PersonnePK("nom1", "prenom1");
Personne personne = entityManager.find(Personne.class, clePersonne);

L'annotation @AttributeOverrides est une collection d'attribut @AttributeOverride. Ces annotations permettent de ne pas avoir à utiliser l'annotation @Column dans la classe de la clé ou de modifier les attributs de cette annotation dans l'entité qui la met en oeuvre.

L'annotation @AttributeOverride possède plusieurs attributs :

Attributs Rôle
name Précise le nom de la propriété de la classe imbriquée
column Précise la colonne de la table à associer à la propriété

Exemple :
package fr.jmdoudoux.dej.jpa;

import java.io.Serializable;

import javax.persistence.AttributeOverrides;
import javax.persistence.AttributeOverride;
import javax.persistence.EmbeddedId;
import javax.persistence.Entity;
import javax.persistence.Column;

@Entity
public class Personne4 implements Serializable {
  @EmbeddedId
  @AttributeOverrides({
    @AttributeOverride(name="nom", column=@Column(name="NOM") ),
    @AttributeOverride(name="prenom", column=@Column(name="PRENOM") )
    })
  private PersonnePK clePrimaire;
  private int taille;
...

Par défaut, toutes les propriétés sont mappées sur la colonne correspondante dans la table. L'annotation @javax.persistence.Transient permet d'indiquer au gestionnaire de persistance d'ignorer cette propriété.

L'annotation @javax.persistence.Basic représente la forme de mapping la plus simple. C'est aussi  celle par défaut ce qui rend son utilisation optionnelle. Ce mapping concerne les types primitifs, les wrappers de type primitifs, les tableaux de ces types et les types java.math.BigInteger, java.math.BigDecimal, java.util.Date, java.util.Calendar, java.sql.Date, java.sql.Time et java.sql.Timestamp.

L'annotation @Basic possède plusieurs attributs :

Attributs Rôle
fetch Permet de préciser comment la propriété est chargée selon deux modes :
  • LAZY : la valeur est chargée uniquement lors de son utilisation
  • EAGER : la valeur est toujours chargée (valeur par défaut)
Cette fonctionnalité permet de limiter la quantité de données obtenue par une requête
optionnal Indique que la colonne est nullable

Généralement, cette annotation peut être omise sauf dans le cas où le chargement de la propriété doit être de type LAZY.

Exemple :
package fr.jmdoudoux.dej.jpa;

import java.io.Serializable;

import javax.persistence.Basic;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;

@Entity
public class Personne implements Serializable {
  @Id
  @GeneratedValue
  private int id;

  @Basic(fetch=FetchType.LAZY, optional=false)
  private String prenom;

  private String nom;
...

L'annotation @javax.persistence.Temporal permet de fournir des informations complémentaires sur la façon dont les propriétés encapsulant des données temporelles (Date et Calendar) sont associées aux colonnes dans la table (date, time ou timestamp). La valeur par défaut est timestamp.

Exemple :
package fr.jmdoudoux.dej.jpa;

import java.io.Serializable;
import java.util.Date;

import javax.persistence.Basic;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;

@Entity
public class Personne implements Serializable {
  @Id
  @GeneratedValue
  private int id;

  @Basic(fetch = FetchType.LAZY, optional = false)
  private String prenom;

  private String nom;

  @Temporal(TemporalType.TIME)
  private Date heureNaissance;

  private static final long serialVersionUID = 1L;

  public Personne() {
    super();
  }

  public int getId() {
    return this.id;
  }

  public void setId(int id) {
    this.id = id;
  }

  public Date getHeureNaissance() {
    return heureNaissance;
  }

  public void setTimeCreated(Date heureNaissance) {
    this.heureNaissance = heureNaissance;
  }
...

 

63.2.2. Le mapping de propriétés complexes

L'API Java persistence permet de mapper des colonnes qui concernent des données de type plus complexe que les types de base tels que les champs blob, clob ou des objets.

L'annotation @javax.persistence.Lob permet de mapper une propriété sur une colonne de type Blob ou Clob selon le type de la propriété :

Fréquemment ce type de propriété est chargé de façon LAZY.

Exemple :
package fr.jmdoudoux.dej.jpa;

import java.io.Serializable;

import javax.persistence.Basic;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Lob;

import com.sun.imageio.plugins.jpeg.JPEG;

@Entity
public class Personne implements Serializable {
  @Id
  @GeneratedValue
  private int id;

  @Basic(fetch = FetchType.LAZY, optional = false)
  private String prenom;

  private String nom;

  @Lob
  @Basic(fetch = FetchType.LAZY)
  private JPEG photo;

  private static final long serialVersionUID = 1L;

  public Personne() {
    super();
  }

  public int getId() {
    return this.id;
  }

  public void setId(int id) {
    this.id = id;
  }

  public JPEG getPhoto() {
    return photo;
  }

  public void setPhoto(JPEG photo) {
    this.photo = photo;
  }
  
  ...

L'annotation  @javax.persistence.Enumerated permet d'associer une propriété de type énumération à une colonne de la table sous la forme d'un numérique ou d'une chaîne de caractères.

Cette forme est précisée en paramètre de l'annotation grâce à l'énumération EnumType qui peut avoir comme valeur EnumType.ORDINAL (valeur par défaut) ou EnumType.STRING.

Exemple : énumération des genres d'une personne
package fr.jmdoudoux.dej.jpa;

public enum Genre {
   HOMME,
   FEMME,
   INCONNU
}

Exemple :
package fr.jmdoudoux.dej.jpa;

import java.io.Serializable;

import javax.persistence.Basic;
import javax.persistence.Entity;
import javax.persistence.EnumType;
import javax.persistence.Enumerated;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;

@Entity
public class Personne implements Serializable {
  @Id
  @GeneratedValue
  private int id;

  @Basic(fetch = FetchType.LAZY, optional = false)
  private String prenom;

  private String nom;

  @Enumerated(EnumType.STRING)
  private Genre genre;

  private static final long serialVersionUID = 1L;

  public Personne() {
    super();
  }

  public int getId() {
    return this.id;
  }

  public void setId(int id) {
    this.id = id;
  }

  public Genre getGenre() {
    return genre;
  }

  public void setGenre(Genre genre) {
    this.genre = genre;
  }

  ...

 

63.2.3. Le mapping d'une entité sur plusieurs tables

Le modèle objet et le modèle relationnel associé ne correspondent pas toujours car les critères de conception ne sont pas forcement les mêmes. Ainsi, il est courant d'avoir une entité qui mappe des colonnes de plusieurs tables.

Exemple : création de la table adresse utilisée dans cette section
ij> create table ADRESSE
(
ID_ADRESSE integer primary key not null,
RUE varchar(250) not null,
CODEPOSTAL varchar(7) not null,
VILLE varchar(250) not null
);
0 lignes insérées/mises à jour/supprimées
ij> INSERT INTO ADRESSE VALUES (1,'rue1','11111','ville1'), (2,'rue2','22222','v
ille2'), (3,'rue3','33333','ville3');
3 lignes insérées/mises à jour/supprimées
ij> select * from adresse;
ID_ADRESSE |RUE
                                                            |CODEPO&|VILLE


--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
-------------------------------------
1          |rue1
                                                            |11111  |ville1


2          |rue2
                                                            |22222  |ville2


3          |rue3
                                                            |33333  |ville3



3 lignes sélectionnées
ij>

L'annotation @javax.persistence.SecondaryTable permet de préciser qu'une autre table sera utilisée dans le mapping.

Pour utiliser cette fonctionnalité, la seconde table doit posséder une jointure entre sa clé primaire et une ou plusieurs colonnes de la première table.

L'annotation @SecondaryTable possède plusieurs attributs :

Attribut Rôle
Name Nom de la table
Catalogue Nom du catalogue
Schema Nom du schéma
pkJoinsColumns Collection des clés primaires de la jointure sous la forme d'annotations de type @PrimaryKeyJoinColumn
uniqueConstraints  

L'annotation @PrimaryKeyJoinColumn permet de préciser une colonne qui compose la clé primaire de la seconde table et entre dans la jointure avec la première table. Elle possède plusieurs attributs :

Attribut Rôle
name Nom de la colonne
referencedColumnName Nom de la colonne dans la première table (obligatoire si les noms de colonnes sont différents entre les deux tables)
columnDefinition  

Il est nécessaire pour chaque propriété de l'entité qui est mappée sur la seconde table de renseigner le nom de la table dans l'attribut table de l'annotation @Column

Exemple : la classe PersonneAdresse
package fr.jmdoudoux.dej.jpa;

import java.io.Serializable;

import javax.persistence.Basic;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.PrimaryKeyJoinColumn;
import javax.persistence.SecondaryTable;
import javax.persistence.Table;

@Entity
@Table(name="PERSONNE")
@SecondaryTable(name="ADRESSE",
pkJoinColumns={
@PrimaryKeyJoinColumn(name="ID_ADRESSE")})
public class PersonneAdresse implements Serializable {
  @Id
  @GeneratedValue
  private int id;

  @Basic(fetch = FetchType.LAZY, optional = false)
  private String prenom;

  private String nom;
  
  @Column(name="RUE", table="ADRESSE")
  private String rue;

  @Column(name="CODEPOSTAL", table="ADRESSE")
  private String codePostal;
  
  @Column(name="VILLE", table="ADRESSE")
  private String ville;
  
  private static final long serialVersionUID = 1L;

  public PersonneAdresse() {
    super();
  }

  public int getId() {
    return this.id;
  }

  public void setId(int id) {
    this.id = id;
  }

  public String getPrenom() {
    return this.prenom;
  }

  public void setPrenom(String prenom) {
    this.prenom = prenom;
  }

  public String getNom() {
    return this.nom;
  }

  public void setNom(String nom) {
    this.nom = nom;
  }

  public String getCodePostal() {
    return codePostal;
  }

  public void setCodePostal(String codePostal) {
    this.codePostal = codePostal;
  }

  public String getRue() {
    return rue;
  }

  public void setRue(String rue) {
    this.rue = rue;
  }

  public String getVille() {
    return ville;
  }

  public void setVille(String ville) {
    this.ville = ville;
  }

}

Résultat :
nom prenom=nom1 prenom1
adresse=rue1, 11111 ville1

Si le mapping d'une entité met en oeuvre plus de deux tables, il faut utiliser l'annotation @javax.persistence.SecondaryTables qui est une collection d'annotations @SecondaryTable

Exemple :
package fr.jmdoudoux.dej.jpa;

import java.io.Serializable;

import javax.persistence.Basic;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.PrimaryKeyJoinColumn;
import javax.persistence.SecondaryTable;
import javax.persistence.SecondaryTables;
import javax.persistence.Table;

@Entity
@Table(name="PERSONNE")
@SecondaryTables({
  @SecondaryTable(name="ADRESSE",
     pkJoinColumns={@PrimaryKeyJoinColumn(name="ID_ADRESSE")}),
  @SecondaryTable(name="INFO_PERS",
     pkJoinColumns={@PrimaryKeyJoinColumn (name="ID_INFO_PERS")})
})
public class PersonneAdresse implements Serializable {
...

 

63.2.4. L'utilisation d'objets embarqués dans les entités

L'API Java Persistence permet d'utiliser dans les entités des objets Java qui ne sont pas des entités  mais qui sont agrégés dans l'entité et dont les propriétés seront mappées sur les colonnes correspondantes dans la table.

La mise en oeuvre de cette fonctionnalité est similaire à celle utilisée avec l'annotation @EmbeddedId pour les clés primaires composées.

La classe embarquée est un simple POJO qui doit être marquée avec l'annotation @javax.persistence.Embeddable

Exemple :
package fr.jmdoudoux.dej.jpa;

import java.io.Serializable;

import javax.persistence.Column;
import javax.persistence.Embeddable;

@Embeddable
public class Adresse implements Serializable{

  private static final long serialVersionUID = 1L;

  @Column(name="RUE", table="ADRESSE")
  private String rue;

  @Column(name="CODEPOSTAL", table="ADRESSE")
  private String codePostal;
  
  @Column(name="VILLE", table="ADRESSE")
  private String ville;
  
  public String getCodePostal() {
    return codePostal;
  }

  public void setCodePostal(String codePostal) {
    this.codePostal = codePostal;
  }

  public String getRue() {
    return rue;
  }

  public void setRue(String rue) {
    this.rue = rue;
  }

  public String getVille() {
    return ville;
  }

  public void setVille(String ville) {
    this.ville = ville;
  }

}

Les propriétés de cette classe peuvent être marquées avec l'annotation @Column au besoin.

Dans l'entité, il faut utiliser l'annotation @javax.persistence.Embedded sur la propriété du type de la classe embarquée.

Exemple :
package fr.jmdoudoux.dej.jpa;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;

public class TestJPA3 {

  public static void main(String[] argv) {
    EntityManagerFactory emf = Persistence
        .createEntityManagerFactory("MaBaseDeTestPU");
    EntityManager em = emf.createEntityManager();
    PersonneAdresse2 personneAdresse = em.find(PersonneAdresse2.class, 1);
    System.out.println("nom prenom="
        + personneAdresse.getNom()
        + " "
        + personneAdresse.getPrenom());
    System.out.println("adresse="
        + personneAdresse.getAdresse().getRue()
        + ", "
        + personneAdresse.getAdresse().getCodePostal()
        + " "
        + personneAdresse.getAdresse().getVille());
    em.close();
    emf.close();
  }
}

L'annotation @AttributesOverride peut être utilisée pour adapter au contexte le mapping des propriétés de l'objet embarqué.

Exemple :
nom prenom=nom1 prenom1
adresse=rue1, 11111 ville1

Si l'annotation @Embedded n'est pas utilisée alors le gestionnaire de persistance va mapper la propriété sur le champ correspondant sous sa forme sérialisée. Avec l'annotation @Embedded chaque propriété de l'objet embarqué est mappée sur la colonne correspondante de la table.

 

63.3. Le fichier de configuration du mapping

Il est aussi possible de définir le mapping dans un fichier de mapping nommé par défaut orm.xml stocké dans le répertoire META-INF.

Ce fichier orm.xml est un fichier au format xml. L'élément racine est le tag <entity-mappings>.

Pour chaque entité, il faut utiliser un tag fils <entity>. Ce tag possède deux attributs :

La déclaration de la clé primaire se fait dans un tag <id> fils d'un tag <attributes>. Ce tag <id> possède un attribut nommé name qui permet de préciser le nom du champ qui est la clé primaire.

 

en construction
La suite de ce chapitre sera développée dans une version future de ce document

 

Le fichier de mapping peut aussi avoir un nom arbitraire mais dans ce cas, il devra être précisé avec le tag <mapping-file> dans le fichier de configuration persistence.xml

 

63.4. L'utilisation du bean entité

Comme c'est un POJO, il est possible d'ajouter des méthodes à la classe mais il est cependant conseillé de maintenir le rôle du bean entity au transfert de données : il faut éviter de lui ajouter des méthodes métiers mais il est possible de définir des méthodes de validation des données qu'il encapsule.

La mise en oeuvre de POJO permet de les utiliser directement lors d'échanges entre le client et le serveur car ils peuvent être sérialisés comme tout objet de base Java.

Les POJO ne servent qu'à définir le mapping et encapsuler des données. L'instanciation d'une entité n'a aucune conséquence sur la table de la base de données mappée avec l'objet.

Toutes les actions de persistance sur ces objets sont réalisées grâce à un objet dédié de l'API : l'EntityManager.

 

63.4.1. L'utilisation du bean entité

Un contexte de persistance (persistence context) est un ensemble d'entités géré par un EntityManager.

Les entités peuvent ainsi être de deux types :

Lorsqu'un contexte de persistance est fermé, toutes les entités du contexte deviennent non gérées.

Il existe deux types de contexte de persistance :

 

63.4.2. L'EntityManager

Les interactions entre la base de données et les beans entités sont assurées par un objet de type javax.persistence.EntityManager : il permet de lire et rechercher des données mais aussi de les mettre à jour (ajout, modification, suppression). L'EntityManager est donc au coeur de toutes les actions de persistance.

Les beans entités étant de simple POJO, leur instanciation se fait comme pour tout autre objet Java. Les données de cette instance ne sont rendues persistantes que par une action explicite demandée à l'EntityManager sur le bean entité.

L'EntityManager assure aussi les interactions avec un éventuel gestionnaire de transactions.

Un EntityManager gère un ensemble défini de beans entités nommé persistence unit. La définition d'un persistence unit est assurée dans un fichier de description nommé persistence.xml.

 

63.4.2.1. L'obtention d'une instance de la classe EntityManager

Lors d'une utilisation dans un conteneur Java EE, il est possible d'obtenir un objet de type EntityManager en utilisant l'injection de dépendance pour l'objet lui-même ou d'obtenir une fabrique de type EntityManagerFactory qui sera capable de créer l'objet.

Dans un environnement Java SE, comme par exemple dans Tomcat ou dans une application de type client lourd, l'instanciation d'un objet de type EntityManager doit être codée.

Sous Java SE, pour obtenir une instance de type EntityManager, il faut utiliser une fabrique de type EntityManagerFactory. Cette fabrique propose la méthode createEntityManager() pour obtenir une instance.

Pour obtenir une instance de la fabrique, il faut utiliser la méthode statique createEntityManagerFactory() de la classe javax.persistence.Persistence qui attend en paramètre le nom de l'unité de persistence à utiliser. Elle va rechercher le fichier persistence.xml dans le classpath et recherche dans ce fichier l'unité de persistance dont le nom est fourni.

Pour libérer les ressources, il faut utiliser la méthode close() de la fabrique une fois que cette dernière n'a plus d'utilité.

Sous Java EE, il est préférable d'utiliser l'injection de dépendance pour obtenir une fabrique ou un contexte de persistance.

L'annotation @javax.persistence.PersistenceUnit sur un champ de type EntityManagerFactory permet d'injecter une fabrique. Cette annotation possède un attribut unitName qui précise le nom de l'unité de persistance.

Exemple :
@PersistenceUnit(unitName="MaBaseDeTestPU")
private EntityManagerFactory factory;

Il est alors possible d'utiliser la fabrique pour obtenir un objet de type EntityManager qui encapsule un contexte de persistence de type extended. Pour associer ce contexte à la transaction courante, il faut utiliser la méthode joinTransaction().

La méthode close() est automatiquement appelée par le conteneur : il ne faut pas utiliser cette méthode dans un conteneur sinon une exception de type IllegalStateException est levée.

L'annotation @javax.persistence.PersistenceContext sur un champ de type  EntityManager permet d'injecter un contexte de persistance. Cette annotation possède un attribut unitName qui précise le nom de l'unité de persistance.

Exemple :
@PersistenceContext(unitName="MaBaseDeTestPU")
private EntityManager entityManager;

 

63.4.2.2. L'utilisation de la classe EntityManager

La méthode contains() de l'EntityManager permet de savoir si une instance fournie en paramètre est gérée par le contexte. Dans ce cas, elle renvoie true, sinon elle renvoie false.

La méthode clear() de l'EntityManager permet de détacher toutes les entités gérées par le contexte. Dans ce cas, toutes les modifications apportées aux entités sont perdues : il est préférable d'appeler la méthode flush() avant la méthode clear() afin de rendre persistante toutes les modifications.

L'appel des méthodes de mise à jour persist(), merge() et remove() ne réalise pas d'actions immédiates dans la base de données sous-jacente. L'exécution de ces actions est à la discrétion de l'EntityManager selon le FlushModeType (AUTO ou COMMIT).

Dans le mode AUTO, les mises à jour sont reportées dans la base de données avant chaque requête. Dans le mode COMMIT, les mises à jour sont reportées dans la base de données lors du commit de la transaction.

Le mode COMMIT est plus performant car il limite les échanges avec la base de données.

Il est possible de forcer l'enregistrement des mises à jour dans la base de données en utilisant la méthode flush() de l'EntityManager.

 

63.4.2.3. L'utilisation de la classe EntityManager pour la création d'une occurrence

Pour insérer une nouvelle entité dans la base de données, il faut :

Exemple :
package fr.jmdoudoux.dej.jpa;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.Persistence;

public class TestJPA4 {
  public static void main(String[]argv) {    
    EntityManagerFactory emf = Persistence.createEntityManagerFactory("MaBaseDeTestPU");    
    EntityManager em = emf.createEntityManager();    
    EntityTransaction transac = em.getTransaction();
    transac.begin();
    Personne nouvellePersonne = new Personne();
    nouvellePersonne.setId(4);
    nouvellePersonne.setNom("nom4");
    nouvellePersonne.setPrenom("prenom4");
    em.persist(nouvellePersonne);
    transac.commit();
    
    em.close();    
    emf.close();  
  }    
}

Remarque : l'exemple ci-dessous utilise JPA dans un environnement qui ne propose aucune fonctionnalité pour assurer les transactions (Java SE) : il est donc nécessaire de créer et gérer manuellement une transaction afin d'assurer la persistance des données.

 

63.4.2.4. L'utilisation de la classe EntityManager pour rechercher des occurrences

Pour effectuer des recherches de données, l'EntityManager propose deux mécanismes :

Pour la recherche par clé primaire, la classe EntityManager possède les méthodes find() et getReference() qui attendent toutes les deux en paramètres un objet de type Class représentant la classe de l'entité et un objet qui contient la valeur de la clé primaire.

La méthode find() renvoie null si l'occurrence n'est pas trouvée dans la base de données.

Exemple :
package fr.jmdoudoux.dej.jpa;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;

public class TestJPA5 {
  public static void main(String[] argv) {
    EntityManagerFactory emf = Persistence
        .createEntityManagerFactory("MaBaseDeTestPU");
    EntityManager em = emf.createEntityManager();

    Personne personne = em.find(Personne.class, 4);
    if (personne != null) {
      System.out.println("Personne.nom=" + personne.getNom());
    }
    em.close();
    emf.close();
  }
}

La méthode getReference() lève une exception de type javax.persistence.EntityNotFoundException si l'occurrence n'est pas trouvée dans la base de données.

Exemple :
package fr.jmdoudoux.dej.jpa;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityNotFoundException;
import javax.persistence.Persistence;

public class TestJPA6 {
  public static void main(String[] argv) {
    EntityManagerFactory emf = Persistence
        .createEntityManagerFactory("MaBaseDeTestPU");
    EntityManager em = emf.createEntityManager();

    try {
    Personne personne = em.getReference(Personne.class, 5);
      System.out.println("Personne.nom=" + personne.getNom());
    } catch (EntityNotFoundException e) {
      System.out.println("personne non trouvée");
    }
    em.close();
    emf.close();
  }
}

 

63.4.2.5. L'utilisation de la classe EntityManager pour rechercher des données par requête

La recherche par requête repose sur des méthodes dédiées de la classe EntityManager (createQuery(), createNamedQuery() et createNativeQuery()) et sur un langage de requête spécifique nommé EJB QL.

L'objet Query encapsule et permet d'obtenir les résultats de son exécution. La méthode getSingleResult() permet d'obtenir un objet unique retourné par la requête.

Exemple :
package fr.jmdoudoux.dej.jpa;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;
import javax.persistence.Query;

public class TestJPA7 {
  public static void main(String[] argv) {
    EntityManagerFactory emf = Persistence
        .createEntityManagerFactory("MaBaseDeTestPU");
    EntityManager em = emf.createEntityManager();

    Query query = em.createQuery("select p from Personne p where p.nom='nom2'");
    Personne personne = (Personne) query.getSingleResult();
    if (personne == null) {
      System.out.println("Personne non trouvée");
    } else {
      System.out.println("Personne.nom=" + personne.getNom());
    }

    em.close();
    emf.close();
  }
}

La méthode getResultList() renvoie une collection qui contient les éventuelles occurrences retournées par la requête.

Exemple :
package fr.jmdoudoux.dej.jpa;

import java.util.List;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;
import javax.persistence.Query;

public class TestJPA8 {
  public static void main(String[] argv) {
    EntityManagerFactory emf = Persistence
        .createEntityManagerFactory("MaBaseDeTestPU");
    EntityManager em = emf.createEntityManager();

    Query query = em.createQuery("select p.nom from Personne p where p.id > 2");
    List noms = query.getResultList();
    for (Object nom : noms) {
      System.out.println("nom = "+nom);
    }
    
    em.close();
    emf.close();
  }
}

L'objet Query gère aussi des paramètres nommés dans la requête. Le nom de chaque paramètre est préfixé par « : » dans la requête. La méthode setParameter() permet de fournir une valeur à chaque paramètre.

Exemple :
package fr.jmdoudoux.dej.jpa;

import java.util.List;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;
import javax.persistence.Query;

public class TestJPA9 {
  public static void main(String[] argv) {
    EntityManagerFactory emf = Persistence
        .createEntityManagerFactory("MaBaseDeTestPU");
    EntityManager em = emf.createEntityManager();

    Query query = em.createQuery("select p.nom from Personne p where p.id > :id");
    query.setParameter("id", 1);
    List noms = query.getResultList();
    for (Object nom : noms) {
      System.out.println("nom = "+nom);
    }
    
    em.close();
    emf.close();
  }
}

 

63.4.2.6. L'utilisation de la classe EntityManager pour modifier une occurrence

Pour modifier une entité existante dans la base de données, il faut :

Exemple :
package fr.jmdoudoux.dej.jpa;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.Persistence;
import javax.persistence.Query;

public class TestJPA10 {
  public static void main(String[] argv) {
    EntityManagerFactory emf = Persistence
        .createEntityManagerFactory("MaBaseDeTestPU");
    EntityManager em = emf.createEntityManager();

    EntityTransaction transac = em.getTransaction();
    transac.begin();

    Query query = em.createQuery("select p from Personne p where p.nom='nom2'");
    Personne personne = (Personne) query.getSingleResult();
    if (personne == null) {
      System.out.println("Personne non trouvée");
    } else {
      System.out.println("Personne.prenom=" + personne.getPrenom());

      personne.setPrenom("prenom2 modifie");
      em.flush();
      
      personne = (Personne) query.getSingleResult();
      System.out.println("Personne.prenom=" + personne.getPrenom());
    }
    
    transac.commit();
    
    em.close();
    emf.close();
  }
}

 

63.4.2.7. L'utilisation de la classe EntityManager pour fusionner des données

L'EntityManager propose la méthode merge() pour fusionner les données d'une entité non gérée avec la base de données. Ceci est particulièrement utile notamment lorsque l'entité est sérialisée pour être envoyée au client : dans ce cas, l'entité n'est plus gérée par le contexte. Lorsque le client renvoie l'entité modifiée, il faut synchroniser les données qu'elle contient avec celles de la base de données. C'est le rôle de la méthode merge().

Exemple :
package fr.jmdoudoux.dej.jpa;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.Persistence;
import javax.persistence.Query;

public class TestJPA11 {
  public static void main(String[] argv) {
    EntityManagerFactory emf = Persistence
        .createEntityManagerFactory("MaBaseDeTestPU");
    EntityManager em = emf.createEntityManager();

    EntityTransaction transac = em.getTransaction();
    transac.begin();

    Query query = em.createQuery("select p from Personne p where p.nom='nom2'");
    Personne personne = (Personne) query.getSingleResult();
    if (personne == null) {
      System.out.println("Personne non trouvée");
    } else {
      System.out.println("Personne.prenom=" + personne.getPrenom());

      Personne pers = new Personne();
      pers.setId(personne.getId());
      pers.setNom(personne.getNom());
      pers.setPrenom("prenom2 REmodifie");
      
      em.merge(pers);
      
      personne = (Personne) query.getSingleResult();
      System.out.println("Personne.prenom=" + personne.getPrenom());
    }
    
    transac.commit();
    
    em.close();
    emf.close();
  }
}

La méthode merge() renvoie une instance gérée de l'entité.

 

63.4.2.8. L'utilisation de la classe EntityManager pour supprimer une occurrence

Pour supprimer une entité existante dans la base de données, il faut :

Exemple :
package fr.jmdoudoux.dej.jpa;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.Persistence;

public class TestJPA12 {
  public static void main(String[] argv) {
    EntityManagerFactory emf = Persistence
        .createEntityManagerFactory("MaBaseDeTestPU");
    EntityManager em = emf.createEntityManager();

    EntityTransaction transac = em.getTransaction();
    transac.begin();

    Personne personne = em.find(Personne.class, 4);    
    if (personne == null) {
      System.out.println("Personne non trouvée");
    } else {
      em.remove(personne);      
    }
    
    transac.commit();
    
    em.close();
    emf.close();
  }
}

La seule façon d'annuler une suppression est de recréer l'entité en utilisant la méthode persist().

 

63.4.2.9. L'utilisation de la classe EntityManager pour rafraîchir les données d'une occurrence

La méthode refresh() de l'EntityManager permet de rafraîchir les données de l'entité avec celles contenues dans la base de données.

Exemple :
package fr.jmdoudoux.dej.jpa;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;

public class TestJPA13 {
  public static void main(String[] argv) {
    EntityManagerFactory emf = Persistence
        .createEntityManagerFactory("MaBaseDeTestPU");
    EntityManager em = emf.createEntityManager();

    Personne personne = em.find(Personne.class, 4);    
    if (personne == null) {
      System.out.println("Personne non trouvée");
    } else {
      em.refresh(personne);      
    }
    
    em.close();
    emf.close();
  }
}

La méthode refresh() peut lever une exception de type EntityNotFoundException si l'occurrence correspondante dans la base de données n'existe plus.

 

63.5. Le fichier persistence.xml

Ce fichier persistence.xml contient la configuration de base pour le mapping notamment en fournissant les informations sur la connexion à la base de données à utiliser.

Le fichier persistence.xml doit être stocké dans le répertoire META-INF

La racine du document XML du fichier persistence.xml est le tag <persistence>.

Il contient un ou plusieurs tags <persistence-unit> qui va contenir les paramètres d'un persistence unit. Ce tag possède deux attributs : name (obligatoire) qui précise le nom de l'unité et qui servira à y faire référence et transaction-type (optionnel) qui précise le type de transaction utilisée (ceci dépend de l'environnement d'exécution : Java SE ou Java EE).

Le tag <persistence-unit> peut avoir les tags fils suivants :

Tag Rôle
<description> Fournir une description purement informative de l'unité de persistance(optionnel)
<provider> Définir le nom pleinement qualifié d'une classe  de type javax.persistence. PersistenceProvider (optionnel). Généralement fournie par le fournisseur de l'implémentation de l'API : une utilisation de ce tag n'est requise que pour des besoins spécifiques
<jta-data-source> Définir le nom JNDI de la DataSource utilisée dans un environnement avec support de JTA (optionnel)
<non-jta-data-source> Définir le nom JNDI de la DataSource utilisée dans un environnement sans support de JTA (optionnel)
<mapping-file> Préciser un fichier de mapping supplémentaire (optionnel)
<jar-file> Préciser un fichier jar qui contient des entités à inclure dans l'unité de persistance : le chemin précisé est relatif au fichier persistence.xml (optionnel)
<class> Préciser une classe d'une entité qui sera incluse dans l'unité de persistence (optionnel)
<properties> Définir des paramètres spécifiques au fournisseur. Comme Java SE ne propose pas de serveur JNDI, c'est fréquemment grâce à ce tag que les informations concernant la source de données sont définies (optionnel)
<exclude-unlisted-classes> Inhiber la recherche automatique des classes des entités (optionnel)

L'ensemble des classes des entités qui compose l'unité de persistance peut être spécifié explicitement dans le fichier persistence.xml ou déterminé dynamiquement à l'exécution par recherche de toutes les classes possédant une annotation @javax.persistence.Entity.

Par défaut, la liste de classes explicite est complétée par la liste des classes issue de la recherche dynamique. Pour empêcher la recherche dynamique, il faut utiliser le tag <exclude-unlisted-classes>. Sous Java SE, il est recommandé de préciser explicitement la liste de classes.

Chaque unité de persistance ne peut être liée qu'à une seule source de données.

Exemple :
<?xml version="1.0"  encoding="UTF-8"?>
<persistence  xmlns="http://java.sun.com/xml/ns/persistence"
              xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
              version="1.0"  xsi:schemaLocation="http://java.sun.com/xml/ns/persistence
              http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd">
  <persistence-unit  name="MaBaseDeTestPU">
    <provider>oracle.toplink.essentials.PersistenceProvider</provider>
    <class>fr.jmdoudoux.dej.jpa.Adresse</class>
    <class>fr.jmdoudoux.dej.jpa.Personne</class>
    <class>fr.jmdoudoux.dej.jpa.PersonneAdresse</class>
    <class>fr.jmdoudoux.dej.jpa.PersonneAdresse2</class>
    <class>fr.jmdoudoux.dej.jpa.PersonnePK</class>
    <properties>
      <property name="toplink.jdbc.driver"  
	      value="org.apache.derby.jdbc.EmbeddedDriver"/>
      <property name="toplink.jdbc.url" 
	      value="jdbc:derby:C:/Program  Files/Java/jdk1.6.0/db/MaBaseDeTest"/>
      <property name="toplink.jdbc.user" value="APP"/>
      <property name="toplink.jdbc.password" value=""/>
      <property name="toplink.logging.level" value="INFO"/>
    </properties>
  </persistence-unit>
</persistence>

 

63.6. La gestion des transactions hors Java EE

Le conteneur Java EE propose un support des transactions grâce à l'API JTA : c'est la façon standard de gérer les transactions par le conteneur.

Hors d'un tel conteneur, par exemple dans une application Java SE, les transactions ne sont pas supportées.

Dans un tel contexte, l'API Java Persistence propose une gestion des transactions grâce à l'interface EntityTransaction.

Cette interface propose plusieurs méthodes :

Méthode Rôle
void begin() Débuter la transaction
void commit() Valider la transaction
void roolback() Annuler la transaction
boolean isActive() Déterminer si la transaction est active

Pour obtenir une instance de la transaction, il faut utiliser la méthode getTransaction() de l'EntityManager.

La méthode begin() lève une exception de type IllegalStateException si une transaction est déjà active.

Les méthodes commit() et rollback() lèvent une exception de type IllegalStateException si aucune transaction n'est active.

Exemple :
package fr.jmdoudoux.dej.jpa;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.Persistence;

public class TestJPA12 {
  public static void main(String[] argv) {
    EntityManagerFactory emf = Persistence
        .createEntityManagerFactory("MaBaseDeTestPU");
    EntityManager em = emf.createEntityManager();

    EntityTransaction transac = em.getTransaction();
    transac.begin();

    Personne personne = em.find(Personne.class, 4);    
    if (personne == null) {
      System.out.println("Personne non trouvée");
    } else {
      em.remove(personne);      
    }
    
    transac.commit();
    
    em.close();
    emf.close();
  }
}

 

63.7. La gestion des relations entre tables dans le mapping

Dans le modèle des bases de données relationnelles, les tables peuvent être liées entre elles grâce à des relations.

Ces relations sont transposées dans les liaisons que peuvent avoir les différentes entités correspondantes.

Les relations peuvent avoir différentes cardinalités :

Chacune de ces relations peut être unidirectionnelle ou bidirectionnelle sauf one-to-many et many-to-one qui sont par définition bidirectionnelles.

 

 

en construction
La suite de ce chapitre sera développée dans une version future de ce document

 

 

63.8. Le mapping de l'héritage de classes

JPA 1.0 propose trois stratégies pour mapper une hiérarchie de classes :

La hiérarchie de classes peut être composée de différentes typologies de classes abstraites ou concrètes :

La classe d'une entité, annotée avec @Entity, peut être abstraite ou concrète.

 

63.8.1. Les annotations

JPA propose plusieurs annotations spécifiques à la définition du mapping d'une hiérarchie de classes.

 

63.8.1.1. L'annotation @MappedSuperclass

L'inconvénient d'une entité dont la classe est abstraite est qu'elle ne peut pas être instanciée et ne peut donc pas être utilisée comme entité par un EntityManager.

JPA propose l'annotation @MappedSuperclass qu'il est possible d'utiliser sur une classe. Cette classe peut alors être utilisée comme classe mère pour des entités. Les champs d'une classe annotée avec @MappedSuperclass sont persistantes dans la base de donnée mais il n'est pas possible de faire des requêtes sur cette classe. Une classe annotée avec @MappedSuperclass ne sera pas mappée sur une table dédiée.

Cette annotation ne possède pas d'attributs.

Exemple :
package fr.jmdoudoux.dej.jpa.entity;

import java.io.Serializable;
import java.util.Date;
import javax.persistence.Column;
import javax.persistence.MappedSuperclass;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;

@MappedSuperclass
public abstract class Audit implements Serializable {

  private static final long serialVersionUID = 1L;
   
  @Column(name = "DAT_INS")
  @Temporal(TemporalType.TIMESTAMP)
  private Date dateInsert;
  
  @Column(name = "USER_INSERT")
  private String userInsert;
  
  @Column(name = "DAT_UPD")
  @Temporal(TemporalType.TIMESTAMP)
  private Date dateUpdate;
  
  @Column(name = "USER_UPDATE")
  private String userUpdate;

  public Date getDateInsert() {
    return dateInsert;
  }

  public void setDateInsert(Date dateInsert) {
    this.dateInsert = dateInsert;
  }

  public Date getDateUpdate() {
    return dateUpdate;
  }

  public void setDateUpdate(Date dateUpdate) {
    this.dateUpdate = dateUpdate;
  }

  public String getUserInsert() {
    return userInsert;
  }

  public void setUserInsert(String userInsert) {
    this.userInsert = userInsert;
  }

  public String getUserUpdate() {
    return userUpdate;
  }

  public void setUserUpdate(String userUpdate) {
    this.userUpdate = userUpdate;
  } 
}

Une entité peut hériter d'une classe annotée avec @MappedSuperclass.

Exemple :
package fr.jmdoudoux.dej.jpa.entity;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Table;

@Entity
@Table(name = "compte")
public class Compte extends Audit {
  private static final long serialVersionUID = 1L;
    
  @Id
  @GeneratedValue
  private Long Id;

  public Long getId() {
    return Id;
  }

  public void setId(Long Id) {
    this.Id = Id;
  }
  
  // ...
}

Certaines informations de mapping dans une classe fille peuvent être redéfinies en utilisant l'annotation @AttributeOverride.

Exemple :
package fr.jmdoudoux.dej.jpa.entity;

import javax.persistence.AttributeOverride;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Table;

@Entity
@Table(name = "compte")
@AttributeOverride(name="dateInsert",column=@Column(name="D_CREA"))
public class Compte extends Audit {
  private static final long serialVersionUID = 1L;
    
  @Id
  @GeneratedValue
  private Long Id;

  public Long getId() {
    return Id;
  }

  public void setId(Long Id) {
    this.Id = Id;
  }
  
  // ...
}

 

63.8.1.2. L'annotation @Inheritance

L'annotation @Inheritance permet de préciser la stratégie de mapping de la hiérarchie de classes à utiliser. Elle s'utilise sur la classe mère qui est la racine de la hiérarchie de classes.

Elle possède un seul attribut nommé strategy du type de l'énumération javax.persistence.InheritanceType.

Il permet de préciser la stratégie à utiliser grâce à une énumération de javax.persistence.InheritanceType. Elle définit trois valeurs :

L'attribut strategy est optionnel : par défaut, si celui-ci n'est pas utilisé alors c'est la stratégie SINGLE_TABLE qui est utilisée.

 

63.8.1.3. L'annotation @DiscriminatorColumn

L'annotation @DiscriminatorColumn permet de définir la colonne qui servira de discriminant dans la table : elle permet de préciser la colonne qui va contenir la valeur utilisée comme discriminant pour savoir à quelle classe correspondent les données. Cette annotation s'utilise pour les stratégies SINGLE_TABLE et JOINED sur la classe mère qui est la racine de la hiérarchie de classes.

L'annotation @DiscriminatorColumn permet de préciser quel champ est le discriminant lors de l'utilisation des stratégies SINGLE_TABLE et JOINED. Elle s'utilise sur la classe mère.

Elle possède plusieurs attributs optionnels :

Attribut

Type

Rôle

columnDefinition

String

Expression SQL du DDL qui permet de définir la colonne. Son utilisation réduit généralement la portabilité de la base de données utilisée (Optionnel). Par défaut, chaîne vide

discriminatorType

javax.persistence.DiscriminatorType

Type de la colonne qui sert de discriminant. La valeur est du type de l'énumération DiscriminatorType (STRING, CHAR, INTEGER) (Optionnel). Par défaut, DiscriminatorType.STRING

length

int

Taille de la colonne si le type est STRING, sinon elle est ignorée (Optionnel). Par défaut, 31

name

String

Nom de la colonne qui sert de discriminant (Optionnel). Par défaut, DTYPE


L'énumération de type DiscriminatorType définit plusieurs valeurs :

Il est recommandé de préciser explicitement l'attribut name.

Si un discriminant est requis par la stratégie et que l'annotation @DiscriminatorColumn n'est pas utilisée alors une colonne nommée «DTYPE» ayant pour type DiscriminatorType.STRING sera utilisée.

 

63.8.1.4. L'annotation @DiscriminatorValue

L'annotation @DiscriminatorValue permet de préciser pour chaque classe la valeur qui sera utilisée dans la colonne servant de discriminant. Elle s'utilise sur une classe concrète de la hiérarchie dont la stratégie de mapping a besoin d'un discriminant.

La valeur fournie sous la forme d'une chaîne de caractères doit pouvoir être convertie dans le type de la colonne.

Si l'annotation @DiscriminatorValue n'est pas utilisée dans une stratégie qui le requiert, alors l'implémentation peut utiliser une valeur appropriée selon le type de la colonne comme discriminant pour chaque classe. Par exemple, si le type est STRING alors c'est le nom de la classe qui sera utilisée par défaut.

 

63.8.2. Des exemples de mises en oeuvre des stratégies

Les exemples de cette section vont mettre en oeuvre les trois stratégies en utilisant MySQL 5.6 comme base de données et Hibernate 4.1 comme implémentation de JPA.

Chaque stratégie utilise la même classe de tests.

Exemple ( code Java 5.0 ) :
import javax.persistence.EntityManager;
import javax.persistence.Persistence;

import fr.jmdoudoux.dej.jpa.heritage.entity.Compte;
import fr.jmdoudoux.dej.jpa.heritage.entity.CompteCourant;
import fr.jmdoudoux.dej.jpa.heritage.entity.CompteEpargne;

public class TestJPAHeritage {

  public static void main(String[] args) {

    EntityManager em = Persistence
        .createEntityManagerFactory("TestJPAHeritage").createEntityManager();

    em.getTransaction().begin();

    Compte compte = em.find(Compte.class, 1);
    System.out.println("Compte=" + compte);

    CompteCourant compteCourant = em.find(CompteCourant.class, 2);
    System.out.println("CompteCourant=" + compteCourant);

    CompteEpargne compteEpargne = em.find(CompteEpargne.class, 3);
    System.out.println("CompteEpargne=" + compteEpargne);

    em.getTransaction().commit();
  }
}

Le fichier META-INF/persistence.xml contient la définition de la persistence unit.

Résultat :
<?xml version="1.0" encoding="UTF-8"?>
<persistence xmlns="http://java.sun.com/xml/ns/persistence"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.0"
    xsi:schemaLocation="http://java.sun.com/xml/ns/persistence
    http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd">
    <persistence-unit name="TestJPAHeritage"
        transaction-type="RESOURCE_LOCAL">
        <provider>org.hibernate.ejb.HibernatePersistence</provider>
        <class>fr.jmdoudoux.dej.jpa.heritage.entity.Compte</class>
        <class>fr.jmdoudoux.dej.jpa.heritage.entity.CompteCourant</class>
        <class>fr.jmdoudoux.dej.jpa.heritage.entity.CompteEpargne</class>
        <properties>
            <property name="javax.persistence.jdbc.url" 
                value="jdbc:mysql://localhost:3307/mabdd"></property>
            <property name="javax.persistence.jdbc.driver" 
                value="com.mysql.jdbc.Driver"></property>
            <property name="javax.persistence.jdbc.user" value="root"></property>
            <property name="javax.persistence.jdbc.password" value="root"></property>
        </properties>
    </persistence-unit>
</persistence>

 

63.8.3. La stratégie une table par hiérarchie de classes (SINGLE_TABLE)

La stratégie utilise une seule table pour stocker les données de toutes les instances de la classe mère et de toutes les classes filles : cette table contient des colonnes pour stocker les propriétés mappées de toutes ces classes.

Résultat :
CREATE DATABASE  IF NOT EXISTS `mabdd` DEFAULT CHARACTER SET utf8;
USE `mabdd`;
DROP TABLE IF EXISTS `compte`;
CREATE TABLE `compte` (
  `discriminant` varchar(31) NOT NULL,
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `numero` varchar(255) DEFAULT NULL,
  `solde` decimal(19,2) DEFAULT NULL,
  `decouvert` int(11) DEFAULT NULL,
  `taux` decimal(19,2) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;

LOCK TABLES `compte` WRITE;
INSERT INTO `compte` VALUES ('Compte',1,'000012345000',0.00,NULL,NULL),
('CompteCourant',2,'000012345010',1200.00,2000,NULL),
('CompteEpargne',3,'000012345020',8000.00,NULL,2.10);
UNLOCK TABLES;

Pour mettre en oeuvre cette stratégie, il faut :

Exemple ( code Java 5.0 ) :
package fr.jmdoudoux.dej.jpa.heritage.entity;

import java.io.Serializable;
import java.math.BigDecimal;
import javax.persistence.Column;
import javax.persistence.DiscriminatorColumn;
import javax.persistence.DiscriminatorType;
import javax.persistence.DiscriminatorValue;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Inheritance;
import javax.persistence.InheritanceType;
import javax.persistence.Table;

@Entity
@Table(name = "compte")
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name = "discriminant", discriminatorType = DiscriminatorType.STRING)
@DiscriminatorValue(value = "compte")
public class Compte implements Serializable {

  private static final long serialVersionUID = 1L;

  @Id
  @GeneratedValue
  @Column(name = "id")
  protected int             id;

  @Column(name = "numero")
  protected String          numero;

  @Column(name = "solde")
  protected BigDecimal      solde;

  public Compte() {
  }

  // getters et setters
  
  @Override
  public String toString() {
    return super.toString() + " [id=" + id + ", numero=" + numero + ", solde="
        + solde + "]";
  }
}

Exemple ( code Java 5.0 ) :
package fr.jmdoudoux.dej.jpa.heritage.entity;
      
import javax.persistence.Column;
import javax.persistence.DiscriminatorValue;
import javax.persistence.Entity;

@Entity
@DiscriminatorValue(value = "compte_courant")
public class CompteCourant extends Compte {

  private static final long serialVersionUID = 1L;

  @Column(name = "decouvert")
  private int               decouvert;

  public int getDecouvert() {
    return decouvert;
  }

  public void setDecouvert(int decouvert) {
    this.decouvert = decouvert;
  }
  
  @Override
  public String toString() {
    return this.getClass().getName() + "@" + System.identityHashCode(this)
        + "CompteCourant [id=" + id + ", numero=" + numero + ", solde=" + solde
        + ", decouvert=" + decouvert + "]";
  }
}

Exemple ( code Java 5.0 ) :
package fr.jmdoudoux.dej.jpa.heritage.entity;
      
import java.math.BigDecimal;
import javax.persistence.Column;
import javax.persistence.DiscriminatorValue;
import javax.persistence.Entity;

@Entity
@DiscriminatorValue(value = "compte")
public class CompteEpargne extends Compte {

  private static final long serialVersionUID = 1L;

  @Column(name = "taux")
  private BigDecimal        taux;
  
  public BigDecimal getTaux() {
    return taux;
  }
  
  public void setTaux(BigDecimal taux) {
    this.taux = taux;
  }
  
  @Override
  public String toString() {
    return this.getClass().getName() + "@" + System.identityHashCode(this)
        + "CompteEpargne [ id=" + id + ", numero=" + numero + ", solde="
        + solde + ", taux=" + taux + "]";
  }
}

Il est possible de mapper la colonne servant de discriminant à une propriété de l'entité : ceci permet d'avoir un accès à sa valeur. Dans ce cas, les attributs insertable et updatable doivent être définis à false dans l'annotation @Column de la propriété.

Exemple ( code Java 5.0 ) :
package fr.jmdoudoux.dej.hibernate.entity;
      
import java.math.BigDecimal;
import javax.persistence.Column;
import javax.persistence.DiscriminatorColumn;
import javax.persistence.DiscriminatorValue;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Inheritance;
import javax.persistence.InheritanceType;
import javax.persistence.Table;

@Entity
@Table(name = "compte")
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name = "discriminant")
@DiscriminatorValue(value = "compte")
public class Compte {
  @Id
  @GeneratedValue
  @Column(name = "id")
  protected int        id;
  
  @Column(name = "numero")
  protected String     numero;
  
  @Column(name = "solde")
  protected BigDecimal solde;
  
  @Column(name = "discriminant", insertable = false, updatable = false)
  protected String     discriminant;
  
  // getters et setters
  
  @Override
  public String toString() {
    return super.toString() + " [id=" + id + ", numero=" + numero + ", solde="
        + solde + "]";
  }
}

 

63.8.4. La stratégie : une classe par classe concrète (TABLE_PER_CLASS)

La stratégie Table per concrete class utilise une table pour chaque entité. Le support de cette stratégie par l'implémentation de JPA est optionnel.

Pour l'exemple de cette section, il y a trois tables qui contiennent les données des différentes classes : chaque entité est mappée sur sa propre table. Les tables sont logiquement liées : les champs hérités existent dans chacune des tables.

Résultat :
CREATE DATABASE  IF NOT EXISTS `mabdd` DEFAULT CHARACTER SET utf8 ;
USE `mabdd`;
DROP TABLE IF EXISTS `compte`;
CREATE TABLE `compte` (
  `id` int(11) NOT NULL,
  `numero` varchar(255) DEFAULT NULL,
  `solde` decimal(19,2) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
LOCK TABLES `compte` WRITE;
INSERT INTO `compte` VALUES (1,'000012345000',0.00);
UNLOCK TABLES;

DROP TABLE
IF EXISTS `compte_courant`;
CREATE TABLE `compte_courant` (
  `id` int(11) NOT NULL,
  `numero` varchar(255) DEFAULT NULL,
  `solde` decimal(19,2) DEFAULT NULL,
  `decouvert` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
LOCK TABLES `compte_courant` WRITE;
INSERT INTO `compte_courant` VALUES (2,'000012345010',1200.00,2000);
UNLOCK TABLES;

DROP TABLE IF EXISTS `compte_epargne`;
CREATE TABLE `compte_epargne` (
  `id` int(11) NOT NULL,
  `numero` varchar(255) DEFAULT NULL,
  `solde` decimal(19,2) DEFAULT NULL,
  `taux` decimal(19,2) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
LOCK TABLES `compte_epargne` WRITE;
INSERT INTO `compte_epargne` VALUES (3,'000012345020',8000.00,2.10);
UNLOCK TABLES;

Pour mettre en oeuvre cette stratégie, il faut :

Cette stratégie de mapping limite le choix dans la stratégie de génération des identifiants à utiliser : les identifiants doivent être uniques malgré leur partage sur plusieurs tables. Il n'est donc pas possible d'utiliser les stratégies AUTO et IDENTITY.

Exemple ( code Java 5.0 ) :
package fr.jmdoudoux.dej.jpa.heritage.entity;

import java.io.Serializable;
import java.math.BigDecimal;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Inheritance;
import javax.persistence.InheritanceType;
import javax.persistence.Table;

@Entity
@Table(name = "compte")
@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
public class Compte implements Serializable {

  private static final long serialVersionUID = 1L;

  @Id
  @GeneratedValue(strategy = GenerationType.TABLE)
  @Column(name = "id")
  protected int             id;
  
  @Column(name = "numero")
  protected String          numero;
  
  @Column(name = "solde")
  protected BigDecimal      solde;
  
  public Compte() {
  }
  
  // getters et setters
  
  @Override
  public String toString() {
    return super.toString() + " [id=" + id + ", numero=" + numero + ", solde="
        + solde + "]";
  }
}

Exemple ( code Java 5.0 ) :
package fr.jmdoudoux.dej.jpa.heritage.entity;
      
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Table;

@Entity
@Table(name = "compte_courant")
public class CompteCourant extends Compte {

  private static final long serialVersionUID = 1L;

  @Column(name = "decouvert")
  private int               decouvert;

  public int getDecouvert() {
    return decouvert;
  }
  
  public void setDecouvert(int decouvert) {
    this.decouvert = decouvert;
  }
  
  @Override
  public String toString() {
    return this.getClass().getName() + "@" + System.identityHashCode(this)
        + "CompteCourant [id=" + id + ", numero=" + numero + ", solde=" + solde
        + ", decouvert=" + decouvert + "]";
  }
}

Exemple ( code Java 5.0 ) :
package fr.jmdoudoux.dej.jpa.heritage.entity;
      
import java.math.BigDecimal;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Table;

@Entity
@Table(name = "compte_epargne")
public class CompteEpargne extends Compte {

  private static final long serialVersionUID = 1L;
  
  @Column(name = "taux")
  private BigDecimal        taux;
  
  public BigDecimal getTaux() {
    return taux;
  }
  
  public void setTaux(BigDecimal taux) {
    this.taux = taux;
  }
  
  @Override
  public String toString() {
    return this.getClass().getName() + "@" + System.identityHashCode(this)
        + "CompteEpargne [ id=" + id + ", numero=" + numero + ", solde="
        + solde + ", taux=" + taux + "]";
  }
}

Il est possible d'utiliser l'annotation @AttributeOverride pour redéfinir le mapping des propriétés héritées. L'annotation @AttributeOverrides permet d'utiliser plusieurs annotations @AttributeOverride.

 

63.8.5. La stratégie une table par sous-classe (JOINED)

La stratégie une table par sous-classe utilise une table par classe fille sur laquelle est effectuée une jointure sur la table de la classe mère pour obtenir toutes les données.

Les tables sont liées entre elles par clés étrangères entre les clés primaires des tables des classes fille et la clé primaire de la table de la classe mère. Les clés primaires des tables des classes filles doivent donc obligatoirement être uniques.

Exemple :
CREATE DATABASE  IF NOT EXISTS `mabdd` DEFAULT CHARACTER SET utf8;
USE `mabdd`;
DROP TABLE IF EXISTS `compte`;
CREATE TABLE `compte` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `numero` varchar(255) DEFAULT NULL,
  `solde` decimal(19,2) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;
LOCK TABLES `compte` WRITE;
INSERT INTO `compte` VALUES (1,'000012345000',0.00),(2,'000012345010',1200.00),
(3,'000012345020',8000.00);
UNLOCK TABLES;

DROP TABLE IF EXISTS `compte_courant`;
CREATE TABLE `compte_courant` (
  `decouvert` int(11) DEFAULT NULL,
  `id` int(11) NOT NULL,
  PRIMARY KEY (`id`),
  KEY `FK_qlp4ap2qk7y2vegbmyrtj2ij2` (`id`),
  CONSTRAINT `FK_qlp4ap2qk7y2vegbmyrtj2ij2`
  FOREIGN KEY (`id`) REFERENCES `compte` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
LOCK TABLES `compte_courant` WRITE;
INSERT INTO `compte_courant` VALUES (2000,2);
UNLOCK TABLES;

DROP TABLE IF EXISTS `compte_epargne`;
CREATE TABLE `compte_epargne` (
  `taux` decimal(19,2) DEFAULT NULL,
  `id` int(11) NOT NULL,
  PRIMARY KEY (`id`),
  KEY `FK_cvpsyxwdfnq90nj88vj93ppvj` (`id`),
  CONSTRAINT `FK_cvpsyxwdfnq90nj88vj93ppvj`
  FOREIGN KEY (`id`) REFERENCES `compte` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
LOCK TABLES `compte_epargne` WRITE;
INSERT INTO `compte_epargne` VALUES (2.10,3);
UNLOCK TABLES;

Pour mettre en oeuvre cette stratégie, il faut :

Si l'annotation@PrimaryKeyJoinColumn n'est pas utilisée sur une classe fille alors la colonne de la clé étrangère est considérée comme équivalente à la clé primaire de la table de la classe mère.

Exemple ( code Java 5.0 ) :
package fr.jmdoudoux.dej.jpa.heritage.entity;

import java.io.Serializable;
import java.math.BigDecimal;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Inheritance;
import javax.persistence.InheritanceType;
import javax.persistence.Table;

@Entity
@Table(name = "compte")
@Inheritance(strategy = InheritanceType.JOINED)
public class Compte implements Serializable {

  private static final long serialVersionUID = 1L;
  @Id
  @GeneratedValue(strategy = GenerationType.TABLE)
  @Column(name = "id")
  protected int             id;
  
  @Column(name = "numero")
  protected String          numero;
  
  @Column(name = "solde")
  protected BigDecimal      solde;
  
  public Compte() {
  }
  
  // getters et setters
  
  @Override
  public String toString() {
    return super.toString() + " [id=" + id + ", numero=" + numero + ", solde="
        + solde + "]";
  }
}

Exemple ( code Java 5.0 ) :
package fr.jmdoudoux.dej.jpa.heritage.entity;
      
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.PrimaryKeyJoinColumn;
import javax.persistence.Table;

@Entity
@Table(name = "compte_courant")
@PrimaryKeyJoinColumn(name = "id")
public class CompteCourant extends Compte {

  private static final long serialVersionUID = 1L;

  @Column(name = "decouvert")
  private int               decouvert;

  public int getDecouvert() {
    return decouvert;
  }

  public void setDecouvert(int decouvert) {
    this.decouvert = decouvert;
  }

  @Override
  public String toString() {
    return this.getClass().getName() + "@" + System.identityHashCode(this)
        + "CompteCourant [id=" + id + ", numero=" + numero + ", solde=" + solde
        + ", decouvert=" + decouvert + "]";
  }
}

Exemple ( code Java 5.0 ) :
package fr.jmdoudoux.dej.jpa.heritage.entity;
      
import java.math.BigDecimal;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.PrimaryKeyJoinColumn;
import javax.persistence.Table;

@Entity
@Table(name = "compte_epargne")
@PrimaryKeyJoinColumn(name = "id")
public class CompteEpargne extends Compte {

  private static final long serialVersionUID = 1L;
  
  @Column(name = "taux")
  private BigDecimal        taux;
  
  public BigDecimal getTaux() {
    return taux;
  }
  
  public void setTaux(BigDecimal taux) {
    this.taux = taux;
  }
  
  @Override
  public String toString() {
    return this.getClass().getName() + "@" + System.identityHashCode(this)
        + "CompteEpargne [ id=" + id + ", numero=" + numero + ", solde="
        + solde + ", taux=" + taux + "]";
  }
}

Il est possible d'utiliser une colonne de type discriminant en utilisant l'annotation @DiscriminatorColumn sur la classe mère pour préciser le nom de cette colonne.

La classe mère peut être abstraite. Dans ce cas, elle doit tout de même être annotée avec @Entity et @Inheritance.

 

63.9. Les callbacks d'événements

L'API Java Persistence permet de définir des callbacks qui seront appelés sur certains événements. Ces callbacks doivent être annotés avec une des annotations définies par JPA dans le package javax.persistence.

Annotation Invocation de la méthode de callback annotée
@PrePersist avant l'ajout d'une nouvelle entity à persiter (ajout à l'EntityManager)
@PostPersist après avoir persisté une nouvelle entité dans la base de données (commit ou flush)
@PostLoad après avoir obtenu une entité de la base de données
@PreUpdate lorsqu'une entité est marquée comme étant modifiée par l'EntityManager
@PostUpdate après qu'une entité est été mise à jour dans la base de données (commit ou flush)
@PreRemove lorsqu'une entité est marquée pour suppression
@PostRemove après d'une entité est été supprimée dans la base de données (commit ou flush)

La classe d'une entité peut inclure des méthodes de callback pour chacun de ses événements du cycle de vie, mais une seule méthode pour le même événement. Une même méthode peut être annotée plusieurs événements en utilisant plusieurs annotations.

Les méthodes de callback dans une classe d'entité peuvent avoir un nom et un modificateur de visibilité libres. Elles ne doivent pas avoir de paramètre et ne rien retourner.

Exemple :
@Entity
public class MonEntite {
    @PrePersist 
    void onPrePersist() {}
    
    @PostPersist
    void onPostPersist() {}
    
    @PostLoad
    void onPostLoad() {}
    
    @PreUpdate
    void onPreUpdate() {}
    
    @PostUpdate
    void onPostUpdate() {}
    
    @PreRemove
    void onPreRemove() {}
    
    @PostRemove
    void onPostRemove() {}
}

Par défaut, une méthode de callback dans une classe mère d'une entité est invoquée pour les instances d'entité de sous-classes, sauf la méthode de callback est redéfinie.

Pour éviter les conflits avec l'action sur la base de données qui a déclenché l'événement du cycle de vie de l'entité (qui peut être en cours), les méthodes de callback ne doivent pas appeler les méthodes de l'EntityManager ou exécuter une Query et ne doivent pas accéder à d'autres entités.

Si une méthode de callback lève une exception, la transaction est marquée pour être annulée avec un rollback.

Il est possible de définir les méthodes de callback dans une classe dédiée qui doit posséder un constructeur par défaut. Dans ce cas, la signature des méthodes doit avoir en paramètre une instance de type Object ou d'un type d'une entité qui sera l'entité à l'origine de l'événement et ne rien retourner

Exemple :
public class EntiteListener {
    @PrePersist 
    void onPrePersist(Object o) {}
    
    @PostPersist
    void onPostPersist(Object o) {}
    
    @PostLoad
    void onPostLoad(Object o) {}
    
    @PreUpdate
    void onPreUpdate(Object o) {}
    
    @PostUpdate
    void onPostUpdate(Object o) {}
    
    @PreRemove
    void onPreRemove(Object o) {}
    
    @PostRemove
    void onPostRemove(Object o) {}
}

L'annotation @EntityListeners permet d'associer la classe listener avec une l'entité.

Exemple :
@Entity 
@EntityListeners(EntiteListener.class)
public class MonEntite {
  // ...
}

Plusieurs classes listener peuvent être associées à l'entité en les passant dans un tableau en attribut de l'annotation @EntityListeners.

 

 

en construction
La suite de ce chapitre sera développée dans une version future de ce document

 


62. Hibernate Partie 9 : La machine virtuelle Java (JVM) Imprimer Index Index avec sommaire Télécharger le PDF    
Développons en Java
v 2.40   Copyright (C) 1999-2023 .