6. Objets inclus

6.1. Introduction

Dans de nombreux cas, mettre des objets en relation est très coûteux et inutile. Coûteux pour deux raisons :
  • Surcharge en lecture et écriture : la lecture entraîne une jointure, ou une requête supplémentaire, l'insertion une requête supplémentaire.
  • Les objets en relations sont stockées dans une table à part, qui peut contenir des quantités très importantes d'objets.
Elle peut être inutile, notamment dans le cas où l'objet en relation partage complètement le cycle de vie de l'objet maître. Dans ce cas, créer une relation @OneToOne est un luxe. JPA propose pour cela une fonctionnalité, qui permet d'inclure les champs de l'objet en relation avec ceux de l'objet maître. Cette technique résout tous les problèmes.

6.2. Déclaration d'un objet inclus

On déclare une classe d'objets inclus en annotant simplement la classe de ces objets avec @Embeddable plutôt qu' @Entity. Les contraintes sur ces classes sont les mêmes : elles doivent être sérializables, et comporter un constructeur vide, par défaut ou explicite. Une entité incluse n'a pas besoin de déclarer de clé primaire : elle n'a pas de table en propre, et partagera donc la même clé primaire que les objets qui la déclarent en relation. Voyons un exemple d'objet inclus.

Exemple 17. Déclaration d'objets inclus

@Embeddable
 public  class Adresse  implements Serializable {

     @Column(length=40)
     private String rue ;

     @ManyToOne
     private Commune commune ;
    
     // reste de la classe
}

On remarque tout d'abord que cette classe n'est pas annotée avec @Entity, il ne s'agit donc pas d'une entité JPA. Elle est annotée avec @Embeddable, et ne pourra être utilisée que dans des relations annotées avec @Embedded. Cette classe va nous permettre d'ajouter une adresse à nos marins. On remarque que l'objet inclus est lui-même en relation avec les communes, ce qui est autorisé.

6.3. Utilisation d'objets inclus

Ajoutons une adresse à nos marins sur l'exemple suivant.

Exemple 18. Utilisation d'objets inclus

@Entity
 public  class Marin  implements Serializable {

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

     @Column(length=40)
     private String name ;

     @Embedded
     private Adresse adresse ;
    
     // reste de la classe
}

Plutôt que d'annoter la relation adresse avec @OneToOne, comme on l'aurait fait pour une relation classique, nous l'avons annotée avec @Embedded, ce qui signifie que les champs de notre adresse seront créés directement dans la table Marin. Un objet référencé dans une relation annotée par @Embedded doit nécessairement être annoté par @Embeddable.
Schéma pour un objet inclus - 1

Figure 14. Schéma pour un objet inclus - 1


On constate que le schéma généré comporte bien la colonne rue et la colonne de jointure vers la table Commune.

6.4. Cas où l'objet inclus est nul

Du point de vue du graphe d'objets rien n'empêche un objet inclus d'être nul : après tout, ce n'est qu'une relation comme les autres, qui peut très bien ne pointer vers rien. Du point de vue de la base de données, le problème est différent : rien ne différencie un marin dont on ne connaît pas l'adresse, d'un marin qui en a une. Si l'on tente d'écrire en base un marin qui n'a pas d'adresse, on risque d'obtenir une exception, dans la mesure où les objets inclus nuls ne sont pas autorisés en JPA. Si l'on lit un marin en base qui n'a pas d'adresse, on obtiendra un objet Java marin, qui possède une adresse dont tous les champs sont vides. On peut mettre en place plusieurs solutions pour régler ce problème :
  • Créer un objet adresse particulier, dont les champs indiquent que cet objet correspond à un objet nul. Cette approche n'est valide que dans certains cas, dans d'autres, une telle configuration de champs n'existe pas.
  • Ajouter à l'objet inclus une colonne technique, booléenne, qui indique que cet objet est en fait nul. Cette approche fonctionne dans tous les cas.
Dans ces deux cas il faut modifier le getter du champ inclus de façon à ce qu'il retourne null quand le champ inclus est en fait nul. Voyons ceci sur un exemple. On notera deux choses sur cet exemple :
  • Le booléen isNull de la classe Adresse est positionné à true par défaut. Donc, toute relation vers une adresse initialisée avec une adresse vide génèrera un retour null sur appel du getter de cette relation.
  • Dans la classe Marin, le champ adresse est initialisé sur une valeur particulière : Adresse.ADRESSE_NULL, de façon à ne jamais être nul. Cela permet d'éviter les éventuelles erreurs si cette relation n'a pas été initialisée. Notons que l'objet Adresse.ADRESSE_NULL génère bien un retour nul du getter associé.

Exemple 19. Champ inclus nul

//
 // Entité Adresse
 //
 @Embeddable
 public  class Adresse  implements Serializable {

     private  boolean isNull = true ;

     private String rue ;

     @ManyToOne
     private Commune commune ;

     // objet adresse utilisé pour l'initialisation
     // le champ isNull de cet objet est true
     public  static ADRESSE_NULL =  new Adresse() ;

     // méthode appelée par les getters
     public  static Adresse getAdresse(Adresse adresse) {
         if (adresse == null || adresse.isNull) {
             return null ;
        }  else {
             return adresse ;
        }
    }
    
     // reste de la classe
}

 //
 // Entité Marin
 //
 @Entity
 public  class Marin  implements Serializable {

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

     @Column(length=40)
     private String name ;

     // le champ est initialisé non nul, afin d'éviter
     // les erreurs à l'écriture
     @Embedded
     private Adresse adresse = Adresse.ADRESSE_NULL ;
    
     // Getter pour l'adresse
     public Adresse getAdresse() {
         return Adresse.getAdresse(this.adresse) ;
    }
    
     // reste de la classe
}

JPA nous crée alors le schéma suivant, nous allons pouvoir enregistrer des valeurs nulles dans notre modèle d'objets Java.
Schéma pour un objet inclus - 2

Figure 15. Schéma pour un objet inclus - 2


6.5. Renommer les colonnes incluses

Il nous reste un dernier problème à traiter : que se passe-t-il si nous devons enregistrer plusieurs instances d'une même classe d'objets inclus dans une entité ? La réponse dépend de l'implémentation, et dans chaque implémentation, peut dépendre de la version que l'on utilise. Si l'implémentation est maligne, elle va se rendre compte qu'il y a une collision de noms, et le gérer en ajoutant des numéros à ces noms. C'est ce que fait Hibernate. Si elle est moins maligne, elle ne va pas voir la collision de nom, et ne pas créer le bon nombre de colonnes. C'est ce que fait EclipseLink 2.0.2. Comme d'habitude dans pareil cas, il faut appliquer le vieux proverbe : on n'est jamais aussi bien servi que par soi-même, et utiliser la possibilité que JPA nous donne de surcharger le nommage des colonnes des objets inclus.

Exemple 20. Renommage des colonnes des objets inclus

@Entity
 public  class Marin  implements Serializable {

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

     @Column(length=40)
     private String name ;

     @Embedded
     @AttributeOverrides({
        @AttributeOverride(
           name="isNull", 
           column=@Column(name="adr_courante_isnull")),
        @AttributeOverride(
           name="rue",    
           column=@Column(name="adr_courante_rue"))
    })
     @AssociationOverrides({
        @AssociationOverride(
           name="commune", 
           joinColumns=@JoinColumn(name="adr_courante_commune_id"))
    })
     private Adresse adresseCourante ;

     @Embedded
     @AttributeOverrides({
        @AttributeOverride(
           name="isNull", 
           column=@Column(name="adr_naissance_courante_isnull")),
        @AttributeOverride(
           name="rue",    
           column=@Column(name="adr_naissance_courante_rue"))
    })
     @AssociationOverrides({
        @AssociationOverride(
           name="commune", 
           joinColumns=@JoinColumn(name="adr_naissance_commune_id"))
    })
     private Adresse adresseDeNaissance ;
    
     // reste de la classe
}

Du point de vue de JPA, la classe Adresse porte deux types de champs : des types de base et des relations. Les types de base sont stockés dans des colonnes, et les relations dans des colonnes de jointure, qui peuvent être rangées dans la même table qu'une autre entité, ou dans une table propre (table de jointure). On renomme une colonne par l'annotation @AttributOverride. Cette annotation prend deux attributs :
  • name, de type String : désigne le nom du champ dans la classe ;
  • column, de type @Column : définit la colonne dans laquelle ce champ sera enregistré. Le type @Column définit lui-même plusieurs attributs, dont name que nous utilisons ici.
Comme la classe Adresse porte deux champs : isNull et rue, nous devons utiliser deux annotations @AttributeOverride. Ces deux annotations sont regroupées dans une annotation @AttributeOverrides (notons le "s" à la fin), placée sur le champ inclus. Reste la relation que Adresse porte, vers Commune. Cette relation est ici enregistrée dans une colonne de jointure, clé étrangère qui référence la clé primaire de la table Commune. On redéfinit le nom de cette colonne en utilisant une annotation @AssociationOverride, qui fonctionne de la même façon que @AttributeOverride. Elle définit trois attributs :
  • name, de type String : désigne le nom du champ dans la classe. Cet attribut est le même que dans @AttributeOverride.
  • joinColumns, de type tableau de @JoinColumn. Effectivement, JPA supporte les clés primaires composites (ce point n'est pas traité ici), et dans ce cas une jointure peut être enregistrée dans plusieurs colonnes. Ici ce tableau ne porte qu'une unique valeur, de type @JoinColumn.
  • joinTable : comme on l'a vu, la jointure peut être enregistrée dans une table de jointure. Dans ce cas, cet attribut nous donne le nom de cette table.
Techniquement, l'annotation @JoinColumn fonctionne comme l'annotation @Column, et définit des attributs équivalents. On obtient alors le schéma suivant.
Schéma pour deux objets inclus

Figure 16. Schéma pour deux objets inclus


6.6. Collections d'objets inclus

Il est possible pour une entité JPA de posséder une collection d'objets @Embeddable. Une telle collection doit être annotée @ElementCollection, comme une collection dont les éléments sont des types de base. Les objets inclus seront alors enregistrés dans une table à part, de la même manière que les types de base.
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