2. Définition d'une entité JPA

2.1. Écriture de l'entité

Une entité JPA est, par définition, une classe Java qui doit avoir les propriétés suivantes :
  • Elle doit posséder un constructeur vide, public ou protected. Rappelons que ce constructeur vide existe par défaut si aucun constructeur n'existe dans la classe. Dans le cas contraire, il doit être ajouté explicitement.
  • Elle ne doit pas être final, et aucune de ses méthodes ne peut être final.
  • Une entité JPA ne peut pas être une interface ou une énumération.
  • Une entité JPA peut être une classe concrête ou abstraite.
Une classe possède des champs. Ces champs sont en général associés à des valeurs en base, rangées dans les colonnes d'une table. L'objet EntityManager est capable de positionner lui-même les valeurs de ces champs. Il le fait par injection, soit directement sur le champ, soit en passant par les getters / setters . Ce point n'est pas spécifié de façon très satisfaisante en JPA 1.0, et a été revu en JPA 2.0. Il est possible de définir des entités JPA sur des classes qui héritent les unes des autres. Sur toute une hiérarchie de classes, on doit avoir une unique définition de clé primaire.

2.2. Annotation de l'entité

Les deux principales annotations que l'on peut mettre sur la déclaration d'une classe sont @Entity, @Table et @Access. Il existe d'autres annotations que nous verrons lorsque nous traiterons l'héritage. L'annotation @Entity nous indique que cette classe est une classe persistante. Elle peut prendre un attribut name, qui fixe le nom de cette entité. Par défaut, le nom d'une entité est le nom complet de sa classe. On peut fixer le nom d'une entité par une constante, en utilisant un import statique. L'annotation @Table permet de fixer le nom de la table dans laquelle les instances de cette classe vont être écrites. Cette annotation est particulièrement utile lorsque l'on doit associer un jeu de classes à des tables existantes. L'annotation @Table supporte plusieurs attributs :
  • Les attributs catalog, schema et name : permettent de fixer les paramètres de la table utilisée.
  • L'attribut @UniqueConstraints permet d'écrire des contraintes d'unicité sur des colonnes ou des groupes de colonnes.
Enfin, l'annotation @Access permet de fixer la façon dont l'entity manager va lire et écrire les valeurs des champs de cette classe. Cette annotation ne prend qu'un seul attribut, qui ne peut prendre que deux valeurs : AccessType.FIELD et AccessType.PROPERTY. Dans le premier cas, les lectures / modifications se font directement sur les champs, dans le second elles passent par les getters / setters . Notons que cette annotation peut aussi être placée sur les champs ou getters d'une classe. On peut donc particulariser l'injection de valeur champ par champ. Voyons ceci sur un exemple de classe Marin.

Exemple 5. Annotations @Entity et @Table

package org.paumard.cours.model;

 import  static org.paumard.cours.model.Marin.MARIN ;

 // reste des imports


 @Entity(name=MARIN)  // le nom de l'entité référence la constante MARIN
 @Table(
    name="marin",
    uniqueConstraints={
        @UniqueConstraint(name="nom_prenom", columnNames={"nom", "prenom"})
    }
)
 @Access(AccessType.FIELD)
 public  class Marin  implements Serializable {

     public  static  final String MARIN =  "Marin" ;

	 // reste de la classe
}

Notons que l'on peut mettre autant de contraintes @UniqueConstraint que l'on veut, séparées par des virgules. Dans notre exemple, le nom de l'entité est fixée par une constante, importée statiquement. La table dans laquelle les instance de cette classe seront écrites se nomme marin. Nous avons écrit une contrainte d'unicité, nommée nom_prenom, qui impose de ne pas avoir deux marins qui portent même nom et même prénom. Enfin, nous avons annoté notre entité de sorte que les lecture / écriture des valeurs de ses champs se fassent par injection directe sur les champs.

2.3. Annotations des champs

D'une façon générale, les champs d'une classe sont automatiquement associés à des colonnes en base, dans la table de l'entité. De plus, ces colonnes portent les mêmes noms que ces champs. Si cette règle par défaut suffit, alors il n'y a pas à annoter les champs. Les types associés automatiquement à des colonnes sont les suivants :
  • Tous les types de base Java : int, long, float, etc...
  • Tous les types enveloppes ( wrappers ) des types de base, et le type String.
  • Les types date java.sql.Date, java.sql.Time et java.sql.TimeStamp. Les types java.util.Date et java.util.Calendar ne sont donc pas associés automatiquement.
  • Les types énumérés.
  • Les types Serializable, qui sont rangés dans des blob .
Il arrive très fréquemment que l'on doive particulariser le type d'application entre un champ d'une classe et une colonne d'une table. Il est notamment très fréquent que le nom choisi par défaut ne convienne pas, on doit donc le fixer à la main. On dispose pour cela de deux annotations : @Column et @Lob. Le cas de l'annotation @Basic, qui s'applique aux champs sera traité dans la suite.

2.3.1. Annotation @Column

Cette annotation peut se poser sur un champ ou sur un getter . Cette position détermine la façon dont l'entity manager injecte les valeurs dans les instances de cette classe. Cette façon de faire est issue de JPA 1.0. On lui préfèrera l'utilisation de @Access, même si elle peut paraître redondante. L'annotation @Column expose les attributs optionnels suivants :
  • name : permet de fixer le nom de la colonne.
  • length : pour les chaînes de caractères, cet attribut permet de fixer la taille du champ SQL associé. Notons que cette taille n'est pas vérifiée dans le code Java.
  • unique : ajoute une contrainte d'unicité sur la colonne.
  • nullable : empêche cette colonne de porter des valeurs nulles. Cette contrainte est utile pour la définition de clés étrangères.
  • insertable, updatable : empêche la modification des valeurs de cette colonne, utilisée pour les clés étrangères.
  • precision, scale : fixe les paramètres d'échelle et de précision des colonnes qui portent des nombres.
  • columnDefinition : permet de donner, en SQL, le code de création d'une colonne. Cet attribut est à utiliser avec précautions, dans la mesure où il impose d'écrire du code SQL natif dans le code Java. Ce code SQL est propre à la base de données cible. C'est précisément ce que l'on ne veut pas faire lorsque l'on fait du JPA !

2.3.2. Annotation @Lob

Cette annotation permet de forcer l'application de cette colonne dans un blob SQL.

2.3.3. Cas des colonnes temporelles

Comme nous l'avons vu, les champs de type java.util.Date et java.util.Calendar ne sont pas associés avec des données en base directement. Pour préciser le type SQL temporel avec lequel on veut stocker ces champs, on dispose d'une annotation spécifique : @Temporal. Cette annotation prend un unique attribut, qui peut prendre les valeurs TemporalType.DATE, TemporalType.TIME ou TemporalType.TIMESTAMP.
  • TemporalType.DATE : enregistre les dates en temps que jour / mois / année ;
  • TemporalType.TIME : enregistre les dates en temps que heure / minute / seconde ;
  • TemporalType.TIMESTAMP : enregistre les deux informations de date et d'heure dans la jouenée.

2.3.4. Cas des colonnes énumérées

Les énumérations Java peuvent être associées à des colonnes de deux façons. Soit l'entity manager crée une colonne numérique, et stocke dedans un entier, qui correspond au numéro d'ordre de la valeur que porte le champ, soit l'entity manager crée une colonne de type chaîne de caractères, et stocke le nom de la valeur énumérée. On spécifie ce point avec l'annotation @Enumerated, qui peut prendre deux valeurs en attribut : EnumType.ORDINAL ou EnumType.STRING.

2.3.5. Cas de la classe Locale

La classe Locale est un cas un peu particulier. Du point de vue technique cette classe encapsule une chaîne de caractères de petite taille : guère plus de cinq ou six caractères, le plus souvent deux ou trois. Mais du point de vue de la spécification, cette classe tombe dans la catégorie des champs Serializable. Elle n'est donc pas prise en compte dans la liste des champs persistés par défaut, et si on l'annote par @Column, elle doit être mappée dans ... un BLOB ! On atteint ici les limites de la spécification JPA, puisque Hibernate associe un tel champ avec une colonne de type varchar (ce qui est une bonne idée), et EclipseLink avec une colonne de type BLOB (ce qui est carrément stupide !).

2.4. Exemple d'utilisation

Voyons tout ceci sur un exemple.

Exemple 6. Entité JPA avec colonnes annotées

package org.paumard.cours.model;

 import  static org.paumard.cours.model.Marin.MARIN ;

 // suite des imports
	
 @Entity(name=MARIN)
 @Table(
    name="marin",
    uniqueConstraints={
        @UniqueConstraint(name="full_name", columnNames={"family_name", "first_name"})
    }
)
 @Access(AccessType.FIELD)
 public  class Marin  implements Serializable {

     public  static  final String MARIN =  "Marin" ;

     private  static  final  long serialVersionUID =  1L;

     @Id
     @GeneratedValue(strategy = GenerationType.AUTO)
     private Long id;

     @Column(name="family_name", length=50)
     private String nom ;

     @Column(name="first_name", length=50)
     private String prenom ;

     private  int age ;

     @Column(name="civility", length=12)
     @Enumerated(EnumType.ORDINAL)
     private Civility civilite ;

     @Column(name="date_de_naissance")
     @Temporal(TemporalType.DATE)
     private Date dateNaissance ;

     @Column(name="code")
     @Lob
     private  byte[] key ;
    
     // reste de la classe
    
}

On notera plusieurs choses sur cet exemple :
  • La contrainte @UniqueConstraint référence bien les noms des colonnes et non pas les noms des champs. Si l'on change les noms des colonnes dans les annotations @Column, alors ils doivent être propagés dans cette contrainte.
  • Les tailles des champs nom et prenom en base sont limités à 50 caractères. Cette limite ne s'applique qu'à la base, ce qui entraînera une troncature du champ s'il est trop long.
  • Le champ age est automatiquement associé à une colonne age dans la table marin.
  • Nous avons choisi d'associer notre champ civilite à une colonne de type chaîne de caractères. Cela rendra le contenu de notre table plus lisible, mais surtout indépendant de l'énumération Civility. Choisir d'écrire en base les numéros d'ordre des éléments énumérés impose effectivement de ne jamais changer ces numéros d'ordre. Cette contrainte, impossible à réaliser dans la pratique, ne se pose pas dans notre cas.
  • On notera enfin l'association manuelle de java.util.Date avec TemporalType.DATE.
JPA & EJB
Retour au blog Java le soir
Cours & Tutoriaux
Table des matières
Introduction
1. Objet du mapping objet / relationnel
2. Un peu d'histoire
Un premier exemple
1. Introduction
2. Création de l'environnement technique
2.1. Introduction
2.2. Création de la base Derby
2.3. Création du projet NetBeans et d'une première entité
2.4. Structure d'un projet persistant
2.5. Une première classe persistante
2.6. Un premier fichier persistence.xml
3. Utilisation de ce premier exemple
3.1. Écriture du code d'utilisation
3.2. Exécution de notre premier exemple
3.3. Modification de la class Marin
3.4. Opérations CRUD
Mettre un jeu de classes en base
1. Introduction
2. Définition d'une entité JPA
2.1. Écriture de l'entité
2.2. Annotation de l'entité
2.3. Annotations des champs
2.4. Exemple d'utilisation
3. Opérations sur les entités
3.1. Introduction
3.2. Opération PERSIST
3.3. Opération REMOVE
3.4. Opération REFRESH
3.5. Opération DETACH
3.6. Opération MERGE
4. Mise en relation d'entités
4.1. Introduction
4.2. Relations unidirectionnelles et bidirectionnelles
4.3. Relation 1:1
4.4. Relation 1:p
4.5. Relation p:1
4.6. Relation n:p
4.7. Comportement cascade
4.8. Effacement des entités orphelines
5. Charger des entités et leurs relations
6. Objets inclus
6.1. Introduction
6.2. Déclaration d'un objet inclus
6.3. Utilisation d'objets inclus
6.4. Cas où l'objet inclus est nul
6.5. Renommer les colonnes incluses
6.6. Collections d'objets inclus
L'API Collection en base
1. Introduction
2. Enregistrer une collection d'entités
2.1. Enregistrement d'une collection simple
2.2. Enregistrement d'un Set
2.3. Enregistrement d'une List
3. Enregistrer une collection de types de base
4. Enregistrement d'une Map
4.1. Table de hachage de type (type de base, entité)
4.2. Cas où la clé est un champ de la valeur
4.3. Cas d'une table (entité, entité)
Héritage
1. Introduction
2. Enregistrement d'une hiérarchie de classes
2.1. Entité et super-classe non enregistrée
2.2. Position du problème
2.3. Trois façons de faire
3. Stratégie SINGLE_TABLE
3.1. Fonctionnement
3.2. Mise en place
3.3. Limitations
4. Stratégie JOINED
4.1. Fonctionnement
4.2. Mise en place
4.3. Limitations
5. Stratégie TABLE_PER_CLASS
5.1. Fonctionnement
5.2. Mise en place
5.3. Limitations
Requêtes
1. Introduction
2. Un premier exemple
2.1. Écriture d'une première requête
2.2. Exécution d'une première requête
2.3. Exécution d'une première requête d'agrégation
3. Définition de requêtes
3.1. Requêtes dynamiques
3.2. Requêtes paramétrées
3.3. Requêtes nommées
3.4. Requêtes natives
4. Exécution, analyse du résultat
4.1. Exécution d'une requête dynamique
4.2. Exécution d'une requête nommée
4.3. Analyse du résultat
4.4. Cas des résultats de grande taille
4.5. Remarques
5. Clause From
5.1. Définition des entités
5.2. Jointures dans la clause From
5.3. Remarque finale sur les jointures en JPQL
6. Clause Where
6.1. Variables et chemins dans une clause where
6.2. Expressions conditionnelles et opérateurs
6.3. Requêtes imbriquées
6.4. Opérateurs any, all et some
6.5. Expressions fonctionnelles
7. Clauses Group By et Having
8. Opérations Update et Delete
EJB
1. Introduction
2. Un premier exemple
2.1. Introduction
2.2. Installation dans Glassfish à l'aide de Netbeans
2.3. Création d'un premier EJB
2.4. Déploiement de notre premier EJB
2.5. Création d'un client
3. Mise en oeuvre du pattern session facade
3.1. Introduction
3.2. Modèle objet
3.3. Définition de l'unité de persistance
3.4. Assemblage de notre application
3.5. Assemblage et déploiement
3.6. Utilisation du client
4. Opération de persistance en façade
4.1. Introduction
4.2. Enrichissement du service
4.3. Création de la méthode findMarinById(long)
4.4. Ajout de la méthode findAllMarins()
4.5. Utilisation dans un code client
5. Types d'EJB
5.1. Introduction
5.2. Qu'est-ce qu'un EJB ?
5.3. Écriture d'un EJB session
5.4. Qu'est-ce qu'une méthode métier ?
5.5. EJB avec ou sans état
5.6. Gestion des transactions
5.7. Restrictions
6. Cycle de vie d'un EJB
6.1. Cas des EJB sans état
6.2. Cas des EJB avec état
6.3. Injection de dépendances
7. Transaction gérée par l'EJB
7.1. Introduction
7.2. Déclaration du mode transactionnel
7.3. Gestion de la transaction
7.4. Fonctionnement de la transaction
7.5. Cas des EJB avec état
8. Transaction gérée par le serveur
8.1. Introduction
8.2. Déclaration du mode transactionnel
8.3. Gestion de la transaction
8.4. Fonctionnement de la transaction
8.5. Remarques
9. Intercepteurs
9.1. Introduction
9.2. Aperçu général
9.3. Cycle de vie d'un intercepteur
9.4. Object InvocationContext
9.5. Interception d'un EJB ou d'une méthode métier
9.6. Exemple de mise en œuvre d'un intercepteur