2. Enregistrement d'une hiérarchie de classes

2.1. Entité et super-classe non enregistrée

Une hiérarchie de classes Java commence toujours par la classe Object, qui est la super-classe de toutes les classes Java que l'on peut écrire. En général, lorsque l'on descend la hiérarchie, on commence par rencontrer des classes abstraites, puis des classes concrètes, de plus en plus spécialisées. Le langage Java ne pose aucune contrainte sur ces hiérarchies : une classe abstraite peut parfaitement étendre une classe concrète. Nous avons vu que pour déclarer une entité JPA, il fallait poser l'annotation @Entity sur la classe. Nous avons en tout trois possibilités pour chaque classe d'une hiérarchie :
  • Poser l'annotation @Entity : dans ce cas la classe est une entité JPA, et ses champs seront enregistrés en base suivant les règles que nous allons voir.
  • Poser l'annotation @MappedSuperClass : dans ce cas la classe n'est pas une entité JPA, mais ses champs seront enregistrés en base, suivant les règles que nous allons voir.
  • Ne poser aucune annotation : dans ce cas la classe n'est pas une entité JPA, et ses champs ne seront pas enregistrés en base.
Ces trois règles s'appliquent indifféremment aux classes abstraites ou concrètes. Une classe @Entity peut étendre ou être étendue par une classe @MappedSuperClass, ou non annotée. Les règles d'enregistrement des champs en base sont très simples :
  • Une classe non annotée est dite "transiente" : ses champs ne sont pas enregistrés en base.
  • Une classe annotée @Entity est dite "persistante" : ses champs sont enregistrés en base dans une table associée à la classe, définie par la stratégie d'enregistrement de l'héritage. Cette classe est une entité, on peut faire des requêtes dessus.
  • Une classe annotée @MappedSuperClass est aussi une classe "persistante", mais ce ne n'est pas une entité, et à ce titre, on ne peut donc pas faire de requêtes dessus. Ses champs sont enregistrés en base, et sont associés aux champs de toutes les entités des sous-classes de cette classe.

2.2. Position du problème

Avant d'examiner les différentes stratégies disponibles, examinons le problème de près sur un exemple.

Exemple 27. Hiérarchie simple

// 
 // Classe Personne
 //
 public  class Personne  {

     private String nom ;

     private String prenom ;
    
     // reste de la classe
}

 //
 // Classe Marin
 //
 public  class Marin  extends Personne {

     private Bateau bateau ;
    
     // reste de la classe
}

Notre application va donc manipuler deux types d'objets : des personnes et des marins. Comment enregistrer ces deux entités en base ? Cette question comporte en fait deux parties :
  • La question immédiate de l'enregistrement des trois champs en base : nom, prenom et bateau : dans quelle table doit-on créer les colonnes associées ?
  • La question sous-jacente du polymorphisme : une collection de Personne peut aussi enregistrer des marins. Peut-on enregistrer cette information en base ?
La première question est la plus simple à traiter, et nous allons commencer par là. Notre entité Personne a besoin de deux colonnes : nom et prenom, que nous pouvons d'emblée enregistrer dans une table Personne. Comment enregistre-t-on le complément d'information amené par la sous-classe Marin ? Trois solutions s'offrent à nous :
  • Enregistrer cette information dans la même table que Personne. Dans ce cas les instances de Marin seront enregistrées dans la même table que Personne, et nous aurons besoin d'une colonne technique pour différencier ces entités.
  • Créer une table Marin. Cette table devra alors récupérer les colonnes associées aux champs de Personne. Notre table Marin comportera trois colonnes : nom, prenom et une colonne de jointure bateau_id.
  • Enregistrer cette information dans une table Marin en jointure de la table Personne. La table Personne n'est pas touchée, et la table Marin ne comporte qu'une colonne de jointure bateau_id.
Ce sont ces trois solutions que nous allons détailler à présent.

2.3. Trois façons de faire

JPA définit trois stratégies pour enregistrer une hiérarchie de classes en base :
  • SINGLE_TABLE : chaque hiérarchie d'entités JPA est enregistrée dans une table unique. Cette stratégie est efficace pour les modèles de faible profondeur d'héritage. Si le nombre d'entités dans une hiérarchie est trop important, les limites de la base de données sous-jacente peuvent le rendre impossible à utiliser.
  • JOINED : chaque entité JPA est enregistrée dans sa propre table. Les entités d'une hiérarchie sont en jointure les unes des autres. Ce modèle est inutilisable dans le cas de hiérarchies trop importantes, pour des raisons de performance. C'est pourtant le seul qui permet de vérifier la première forme normale.
  • TABLE_PER_CLASS : seules les entités associées à des classes concrètes sont enregistrées dans leur propre table. Ce modèle est un compromis honnête entre les deux approches précédentes, notamment dans le cas des hiérarchies importantes. Si l'on réfléchit bien à la distribution des classes concrètes du modèle, et des super-classes non enregistrées, il peut même se révéler très efficace.
Chacune de ces stratégies présente des avantages et des inconvénients, du point de vue de la structure, du respect des formes normales, ou des performances. Examinons-les une par une.
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