2. Enregistrer une collection d'entités

2.1. Enregistrement d'une collection simple

Nous avons vu au chapitre précédent que l'enregistrement d'une collecion simple pouvait se faire en annotant cette relation simplement par @OneToMany ou @ManyToMany. Si cette relation n'est pas bidirectionnelle, JPA crée une table de jointure afin de porter cette relation. Dans l'API Collection, la Collection n'a pas de sémantique particulière. Il s'agit juste d'un ensemble d'objets Java sans contrainte, ni d'unicité, ni d'ordonnancement.

2.2. Enregistrement d'un Set

Un Set a une sémantique particulière : il ne peut pas comporter de doublon. Cette notion impose de définir l'égalité entre objets, ce qui se fait par les méthodes equals() et hashCode(). L'implémentation de Set qui nous est fournie dans le JDK est HashSet. Il s'agit en fait d'une table de hachage, dont les clés sont les codes de hachage des objets ajoutés au Set. Si l'on n'a pas respecté le contrat de la classe Object, à savoir que deux objets égaux au sens de equals() doivent avoir même code de hachage, alors le fonctionnement du HashSet ne sera pas celui que l'on attend. La bonne nouvelle est que la sémantique du Set fonctionne en JPA. Si l'on tente d'ajouter un élément à un Set qui s'y trouve déjà, cet ajout échouera, et aucune modification ne sera faite en base.

2.3. Enregistrement d'une List

La sémantique de la List est différente de celle du Set. On peut ajouter plusieurs fois le même élément dans une liste, sans que cela ne pose de problème. La contrainte est que, lorsque l'on relit les éléments d'une liste, alors on doit obtenir ces éléments dans le même ordre que celui dans lequel on les a enregistrés. En d'autres termes, une liste conserve l'ordre dans lequel les éléments ont été ajoutés. Ce point est réellement problématique en JPA, et d'ailleurs n'est pas supporté en JPA 1.0. Lorsque l'on pose une annotation @OneToMany sur une relation de type liste, sans autre précision, alors l'ordre des éléments de cette liste sera perdu après une relecture en base. On peut spécifier un ordonnancement des éléments de deux façons.
  • On peut choisir un champ particulier de ces éléments, et indiquer à JPA que lorsque l'on parcourt cette liste, alors ses éléments doivent être triés par ordre croissant ou décroissant de cet élément. Cette fonctionnalité est disponible en JPA 1.0.
  • On peut demander à JPA de créer une colonne technique, dans laquelle il va entretenir un index, qui permettra de conserver la sémantique Java sur cette liste. Cette fonctionnalité n'est pas disponible en JPA 1.0, elle n'existe qu'à partir de JPA 2.0.

2.3.1. Ordonner les éléments par un de leurs champs

Voyons ceci sur un exemple.

Exemple 21. Ordonner les éléments d'une liste par un de leurs champs

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

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

     @OneToMany
     @OrderBy("name")  // doit être un champ de Marin !
     private List<Marin> marins ;
    
     // suite 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 ;
    
     // suite de la classe
}

Notre entité Bateau porte une relation marins, multivaluée, de type 1:p. Cela signifie qu'un bateau peut posséder plusieurs marins par cette relation, et qu'un marin donné ne peut appartenir qu'à un unique bateau. Notre champ Java est déclaré comme étant de type List, mais la sémantique attachée à ce type n'est pas respectée par JPA. Les marins que l'on enregistre dans cette liste ont donc toutes les chances d'être relus dans un ordre aléatoire. Pour pallier ce défaut, JPA 1.0 introduit l'attribut OrderBy, que nous utilisons ici. Cet attribut doit désigner un champ de l'entité cible de cette relation, ici Marin. À chaque lecture de la relation marins, JPA ordonnera nos marins en fonction de leur nom, garantissant ainsi un ordre fixe du contenu de la liste. Cet ordre n'est toutefois pas nécessairement le même que celui dans lequel nos marins ont été enregistrés. La sémantique Java n'est donc toujours pas exactement respectée. L'utilisation de cet attribut n'a pas d'impact sur la structure de base générée. Seules les requêtes SQL sont modifiées s'il est présent. Si l'on persiste une telle liste, elle ne se triera pas magiquement dans la mémoire de notre application ! D'une façon générale, cette annotation n'impose pas l'ordre des éléments dans la liste marins de nos objets bateau. Elle ne fait qu'imposer l'ordre de ces éléments lors de la lecture en base. L'argument de @OrderBy peut être une liste de champs de l'entité cible, séparés par des virgules. On peut ajouter le mot-clé ASC ou DESC après chacun de ces champs, afin de préciser si le classement doit se faire dans l'ordre croissant ou décroissant. De plus, on prendra garde au fait que cet ordre ne s'applique qu'au moment où l'on lit cette liste de la base. En particulier, si l'on remplit cette liste avec des valeurs rangées dans un ordre qui ne respecte pas l'annotation, JPA ne fait rien pour réordonnancer la liste. Lors de l'itération suivante, même si elle a été mise en base, l'ordre dans lequel les objets seront lus par itération sera celui dans lequel on les a mis.

2.3.2. Ordonner les éléments par ajout d'une colonne technique

JPA 2.0 propose un respect strict de la sémantique de la List Java. Voyons ceci sur un exemple.

Exemple 22. Ordonner les éléments d'une liste à l'aide du colonne technique

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

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

     @OneToMany
     @OrderColumn(name="marins_order")
     private List<Marin> marins ;
    
     // suite 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 ;
    
     // suite de la classe
}

Cet exemple est le même que le précédent, sauf que nous avons utilisé l'annotation @OrderColumn au lieu de l'attribut orderBy. Cette annotation ajoute une colonne technique, nommée marins_order, utilisée pour enregistrer l'ordre dans lequel nos marins ont été enregistrés. Dans le cas de la présence d'une table de jointure (cas @OneToMany unidirectionnel, ou @ManyToMany), cette colonne technique est présente dans la table de jointure, comme on le voit sur la structure que JPA génère pour notre exemple.
Schéma pour une relation multivaluée avec table de jointure ordonnée par une colonne technique

Figure 17. Schéma pour une relation multivaluée avec table de jointure ordonnée par une colonne technique


Dans le cas où nous n'avons pas de table de jointure (relation @OneToMany bidirectionnelle), alors la colonne technique est créée dans la table qui porte la colonne de jointure, c'est-à-dire la table de l'entité maître de la relation.
Schéma pour une relation multivaluée ordonnée par une colonne technique sans table de jointure

Figure 18. Schéma pour une relation multivaluée ordonnée par une colonne technique sans table de jointure


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