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é.


JPA nous crée alors le schéma suivant, nous allons pouvoir enregistrer des valeurs nulles dans notre modèle d'objets Java.


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.


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.


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