Mapping des collections Collections persistantes Hibernate requiert que les champs contenant des collections persistantes soient déclarés comme des types d'interface, par exemple : L'interface réelle devrait être java.util.Set, java.util.Collection, java.util.List, java.util.Map, java.util.SortedSet, java.util.SortedMap ou ... n'importe quoi d'autre ! (Où "n'importe quoi d'autre" signifie que vous devrez écrire une implémentation de org.hibernate.usertype.UserCollectionType.) Notez comment nous avons initialisé les variables d'instance avec une instance de HashSet. C'est le meilleur moyen pour initialiser les collections d'instances nouvellement créées (non persistantes). Quand nous fabriquons l'instance persistante - en appelant persist(), par exemple - Hibernate remplacera réellement le HashSet avec une instance d'une implémentation propre à Hibernate de Set. Prenez garde aux erreurs : Les collections persistantes injectées par Hibernate se comportent de la même manière que HashMap, HashSet, TreeMap, TreeSet ou ArrayList, selon le type de l'interface. Les instances des collections ont le comportement habituel des types des valeurs. Elles sont automatiquement persistées quand elles sont référencées par un objet persistant et automatiquement effacées quand elles sont déréférencées. Si une collection est passée d'un objet persistant à un autre, ses éléments pourraient être déplacés d'une table à une autre. Deux entités ne peuvent pas partager une référence vers une même instance d'une collection. Dû au modèle relationnel sous-jacent, les propriétés contenant des collections ne supportent pas la sémantique de la valeur null ; Hibernate ne distingue pas une référence vers une collection nulle d'une collection vide. Vous ne devriez pas vous préoccuper trop de ça. Utilisez les collections persistantes de la même manière que vous utilisez des collections Java ordinaires. Assurez-vous de comprendre la sémantique des associations bidirectionnelles (traitée plus loin). Mapper une collection L'élément de mapping d'Hibernate utilisé pour mapper une collection dépend du type de l'interface. Par exemple, un élément <set> est utilisé pour mapper des propriétés de type Set. ]]> À part <set>, il y aussi les éléments de mapping <list>, <map>, <bag>, <array> et <primitive-array>. L'élément <map> est représentatif : ]]> name : le nom de la propriété contenant la collection table (optionnel - par défaut = nom de la propriété) : le nom de la table de la collection (non utilisé pour les associations one-to-many) schema (optionnel) : le nom du schéma pour surcharger le schéma déclaré dans l'élément racine lazy (optionnel - par défaut = true) : peut être utilisé pour désactiver l'initialisation tardive et spécifier que l'association est toujours rapportée, ou pour activer la récupération extra-paresseuse (NdT : extra-lazy) où la plupart des opérations n'initialisent pas la collection (approprié pour de très grosses collections) inverse (optionnel - par défaut = false) : définit cette collection comme l'extrêmité "inverse" de l'association bidirectionnelle cascade (optionnel - par défaut = none) : active les opérations de cascade vers les entités filles sort (optionnel) : spécifie une collection triée via un ordre de tri naturel, ou via une classe comparateur donnée (implémentant Comparator) order-by (optionnel, seulement à partir du JDK1.4) : spécifie une colonne de table (ou des colonnes) qui définit l'ordre d'itération de Map, Set ou Bag, avec en option asc ou desc where (optionnel) : spécifie une condition SQL arbitraire WHERE à utiliser au chargement ou à la suppression d'une collection (utile si la collection ne doit contenir qu'un sous ensemble des données disponibles) fetch (optionnel, par défaut = select) : à choisir entre récupération par jointures externes, récupération par selects séquentiels, et récupération par sous-selects séquentiels batch-size (optionnel, par défaut = 1) : une taille de batch (batch size) utilisée pour charger plusieurs instances de cette collection en initialisation tardive access (optionnel - par défaut = property) : La stratégie qu'Hibernate doit utiliser pour accéder à la valeur de la propriété optimistic-lock (optionnel - par défaut = true) : spécifie que changer l'état de la collection entraîne l'incrémentation de la version appartenant à l'entité (Pour une association un vers plusieurs, il est souvent raisonnable de désactiver ce paramètre) mutable (optionnel - par défaut = true) : une valeur à false spécifie que les éléments de la collection ne changent jamais (une optimisation mineure dans certains cas) Les clefs étrangères d'une collection Les instances d'une collection sont distinguées dans la base par la clef étrangère de l'entité qui possède la collection. Cette clef étrangère est référencée comme la(es) colonne(s) de la clef de la collection de la table de la collection. La colonne de la clef de la collection est mappée par l'élément <key>. Il peut y avoir une contrainte de nullité sur la colonne de la clef étrangère. Pour les associations unidirectionnelles un vers plusieurs, la colonne de la clef étrangère peut être nulle par défaut, donc vous pourriez avoir besoin de spécifier not-null="true". ]]> La contraite de la clef étrangère peut utiliser ON DELETE CASCADE. ]]> Voir le chapitre précédent pour une définition complète de l'élément <key>. Les éléments d'une collection Les collections peuvent contenir la plupart des autres types Hibernate, dont tous les types basiques, les types utilisateur, les composants, et bien sûr, les références vers d'autres entités. C'est une distinction importante : un objet dans une collection pourrait être géré avec une sémantique de "valeur" (sa durée de vie dépend complètement du propriétaire de la collection) ou il pourrait avoir une référence vers une autre entité, avec sa propre durée de vie. Dans le dernier cas, seul le "lien" entre les 2 objets est considéré être l'état retenu par la collection. Le type contenu est référencé comme le type de l'élément de la collection. Les éléments de la collections sont mappés par <element> ou <composite-element>, ou dans le cas des références d'entité, avec <one-to-many> ou <many-to-many>. Les deux premiers mappent des éléments avec un sémantique de valeur, les deux suivants sont utilisés pour mapper des associations d'entité. Collections indexées Tous les mappings de collection, exceptés ceux avec les sémantiques d'ensemble (NdT : set) et de sac (NdT : bag), ont besoin d'une colonne d'index dans la table de la collection - une colonne qui mappe un index de tableau, ou un index de List, ou une clef de Map. L'index d'une Map peut être n'importe quel type basique, mappé avec <map-key>, ça peut être une référence d'entité mappée avec <map-key-many-to-many>, ou ça peut être un type composé, mappé avec <composite-map-key>. L'index d'un tableau ou d'une liste est toujours de type integer et est mappé en utilisant l'élément <list-index>. Les colonnes mappées contiennent des entiers séquentiels (numérotés à partir de zéro par défaut). ]]> nom_de_colonne (requis) : le nom de la colonne contenant les valeurs de l'index de la collection base (optionnel, par défaut = 0) : la valeur de la colonne de l'index qui correspond au premier élément de la liste ou du tableau ]]> column (optionnel) : le nom de la colonne contenant les valeurs de l'index de la collection formula (optionnel) : une formule SQL utilisée pour évaluer la clef de la map type (reguis): le type des clefs de la map ]]> column (optionnel) : le nom de la colonne de la clef étrangère pour les valeurs de l'index de la collection formula (optionnel) : une formulre SQL utilisée pour évaluer la clef étrangère de la clef de la map class (requis): la classe de l'entité utilisée comme clef de la map Si votre table n'a pas de colonne d'index, et que vous souhaitez tout de même utiliser List comme type de propriété, vous devriez mapper la propriété comme un <bag> Hibernate. Un sac (NdT : bag) ne garde pas son ordre quand il est récupéré de la base de données, mais il peut être optionnellement trié ou ordonné. Il y a pas mal de variétés de mappings qui peuvent être générés pour les collections, couvrant beaucoup des modèles relationnels communs. Nous vous suggérons d'expérimenter avec l'outil de génération de schéma pour avoir une idée de comment traduire les différentes déclarations de mapping vers des table de la base de données. Collections de valeurs et associations plusieurs-vers-plusieurs N'importe quelle collection de valeurs ou association plusieurs-vers-plusieurs requiert une table de collection avec une(des) colonne(s) de clef étrangère, une(des) colonne(s) d'élément de la collection ou des colonnes et possiblement une(des) colonne(s) d'index. Pour une collection de valeurs, nous utilisons la balise <element>. ]]> column (optionnel) : le nom de la colonne contenant les valeurs de l'élément de la collection formula (optionnel) : une formule SQL utilisée pour évaluer l'élément type (requis) : le type de l'élément de la collection Une association plusieurs-vers-plusieurs est spécifiée en utilisant l'élément <many-to-many>. ]]> column (optionnel) : le nom de la colonne de la clef étrangère de l'élément formula (optionnel) : une formule SQL utilisée pour évaluer la valeur de la clef étrangère de l'élément class (requis) : le nom de la classe associée fetch (optionnel - par défaut join) : active les récupérations par jointures externes ou par selects séquentiels pour cette association. C'est un cas spécial ; pour une récupération complète sans attente (dans un seul SELECT) d'une entité et de ses relations plusieurs-vers-plusieurs vers d'autres entités, vous devriez activer la récupération join non seulement sur la collection elle-même, mais aussi avec cet attribut sur l'élément imbriqué <many-to-many>. unique (optionnel) : activer la génération DDL d'une contrainte d'unicité pour la colonne de la clef étrangère. Ça rend la pluralité de l'association effectivement un-vers-plusieurs. not-found (optionnel - par défaut exception) : spécifie comment les clefs étrangères qui référencent la lignes manquantes seront gérées : ignore traitera une ligne manquante comme une association nulle. entity-name (optionnel) : le nom de l'entité de la classe associée, comme une alternative à class property-ref (optionnel) : le nom d'une propriété de la classe associée qui est jointe à cette clef étrangère. Si non spécifiée, la clef primaire de la classe associée est utilisée. Quelques exemples, d'abord, un ensemble de chaînes de caractères : ]]> Un bag contenant des entiers (avec un ordre d'itération déterminé par l'attribut order-by) : ]]> Un tableau d'entités - dans ce cas, une association plusieurs-vers-plusieurs : ]]> Une map de chaînes de caractères vers des dates : ]]> Une liste de composants (discute dans le prochain chapitre) : ]]> Association un-vers-plusieurs Une association un vers plusieurs lie les tables de deux classes par une clef étrangère, sans l'intervention d'une table de collection. Ce mapping perd certaines sémantiques des collections Java normales : Une instance de la classe de l'entité contenue ne peut pas appartenir à plus d'une instance de la collection Une instance de la classe de l'entité contenue ne peut pas apparaître plus plus d'une valeur d'index de la collection Une association de Product vers Part requiert l'existence d'une clef étrangère et possiblement une colonne d'index pour la table Part. Une balise <one-to-many> indique que c'est une association un vers plusieurs. ]]> class (requis) : le nom de la classe associée not-found (optionnel - par défaut exception) : spécifie comment les identifiants cachés qui référencent des lignes manquantes seront gérés : ignore traitera une ligne manquante comme une association nulle entity-name (optionnel) : le nom de l'entité de la classe associée, comme une alternative à class. Notez que l'élément <one-to-many> n'a pas besoin de déclarer de colonnes. Il n'est pas non plus nécessaire de spécifier le nom de la table nulle part. Note très importante : si la colonne de la clef d'une association <one-to-many> est déclarée NOT NULL, vous devez déclarer le mapping de <key> avec not-null="true" ou utiliser une association bidirectionnelle avec le mapping de la collection marqué inverse="true". Voir la discussion sur les associations bidirectionnelles plus tard dans ce chapitre. Cet exemple montre une map d'entités Part par nom (où partName est une propriété persistante de Part). Notez l'utilisation d'un index basé sur une formule. ]]> Mappings de collection avancés Collections triées Hibernate supporte des collections implémentant java.util.SortedMap et java.util.SortedSet. Vous devez spécifier un comparateur dans le fichier de mapping : ]]> Les valeurs permises pour l'attribut sort sont unsorted, natural et le nom d'une classe implémentant java.util.Comparator. Les collections triées se comportent réellement comme java.util.TreeSet ou java.util.TreeMap. Si vous voulez que la base de données elle-même ordonne les éléments de la collection, utilisez l'attribut order-by des mappings set, bag ou map. Cette solution est seulement disponible à partir du JDK 1.4 (c'est implémenté en utilisant LinkedHashSet ou LinkedHashMap). Ceci exécute le tri dans la requête SQL, pas en mémoire. ]]> Notez que la valeur de l'attribut order-by est un ordre SQL, pas un ordre HQL ! Les associations peuvent même être triées sur des critères arbitraires à l'exécution en utilisant un filter() de collection. Associations bidirectionnelles Une association bidirectionnelle permet une navigation à partir de la "fin" de l'association. Deux sortes d'associations bidirectionnelles sont supportées : un-vers-plusieurs (NdT : one-to-many) ensemble ou sac à une extrémité, une seule valeur à l'autre plusieurs-vers-plusieurs (NdT : many-to-many) ensemble ou sac aux deux extrémités Vous pouvez spécifier une association plusieurs-vers-plusieurs bidirectionnelle simplement en mappant deux associations plusieurs-vers-plusieurs vers la même table de base de données et en déclarant une extrémité comme inverse (celle de votre choix, mais ça ne peut pas être une collection indexée). Voici un exemple d'association bidirectionnelle plusieurs-vers-plusieurs ; chaque catégorie peut avoir plusieurs objets et chaque objet peut être dans plusieurs catégories : ... ... ]]> Les changements faits uniquement sur l'extréminté inverse de l'association ne sont pas persistés. Ceci signifie qu'Hibernate a deux représentations en mémoire pour chaque association bidirectionnelles, un lien de A vers B et un autre de B vers A. C'est plus facile à comprendre si vous pensez au modèle objet de Java et comment nous créons une relation plusieurs-vers-plusieurs en Java : La partie non-inverse est utilisée pour sauvegarder la représentation en mémoire dans la base de données. Vous pouvez définir une association un-vers-plusieurs bidirectionnelle en mappant une association un-vers-plusieurs vers la(es) même(s) colonne(s) de table qu'une association plusieurs-vers-un et en déclarant l'extrémité pluri-valuée inverse="true". .... .... ]]> Mapper une extrémité d'une association avec inverse="true" n'affecte pas l'opération de cascades, ce sont des concepts orthogonaux ! Associations bidirectionnelles avec des collections indexées Une association bidirectionnelle où une extrémité est représentée comme une <list> ou une <map> requiert une considération spéciale. Si il y a une propriété de la classe enfant qui mappe la colonne de l'index, pas de problème, nous pouvons continuer à utiliser inverse="true" sur le mapping de la collection : .... .... ]]> Mais, si il n'y a pas de telle prorpriété sur la classe enfant, nous ne pouvons pas penser à l'association comme vraiment bidirectionnelle (il y a des informations disponibles à une extrémité de l'association qui ne sont pas disponibles à l'autre extrémité). Dans ce cas, nous ne pouvons pas mapper la collection inverse="true". À la place, nous pourrions utiliser le mapping suivant : .... .... ]]> Notez que dans ce mapping, l'extrémité de l'association contenant la collection est responsable des mises à jour de la clef étrangère. À faire : cela entraîne-t-il réellement des expressions updates inutiles ? Associations ternaires Il y a trois approches possibles pour mapper une association ternaire. L'une est d'utiliser une Map avec une association en tant qu'index : ]]> ]]> Une seconde approche est simplement de remodeler l'association comme une classe d'entité. C'est l'approche la plus commune. Une alternative finale est d'utiliser des éléments composites, dont nous discuterons plus tard. Utiliser un <literal><idbag></literal> Si vous embrassez pleinement notre vue que les clefs composées sont une mauvaise chose et que des entités devraient avoir des identifiants artificiels (des clefs subrogées), alors vous pourriez trouver un peu curieux que les associations plusieurs-vers-plusieurs et les collections de valeurs que nous avons montré jusqu'ici mappent toutes des tables avec des clefs composées ! Maintenant, ce point est assez discutable ; une table d'association pure ne semble pas beaucoup bénéficier d'une clef subrogée (bien qu'une collection de valeur composées le pourrait). Néanmoins, Hibernate fournit une foncionnalité qui vous permet de mapper des associations plusieurs-vers-plusieurs et des collections de valeurs vers une table avec une clef subrogée. L'élément <idbag> vous laisse mapper une List (ou une Collection) avec une sémantique de sac. ]]> Comme vous pouvez voir, un <idbag> a un généréteur d'id artificiel, comme une classe d'entité ! Une clef subrogée différente est assignée à chaque ligne de la collection. Cependant, Hibernate ne fournit pas de mécanisme pour découvrir la valeur d'une clef subrogée d'une ligne particulière. Notez que les performances de la mise à jour d'un <idbag> sont bien meilleures qu'un <bag> ordinaire ! Hibernate peut localiser des lignes individuelles efficacement et les mettre à jour ou les effacer individuellement, comme une liste, une map ou un ensemble. Dans l'implémentation actuelle, la stratégie de la génération de l'identifiant native n'est pas supportée pour les identifiants de collection <idbag>. Exemples de collections Les sections précédentes sont assez confuses. Donc prenons un exemple. Cette classe : a une collection d'instances de Child. Si chaque enfant a au plus un parent, le mapping le plus naturel est une association un-vers-plusieurs : ]]> Ceci mappe les définitions de tables suivantes : Si le parent est requis, utilisez une association un-vers-plusieurs unidirectionnelle : ]]> Notez la contrainte NOT NULL : Alternativement, si vous insistez absolument pour que cette association soit unidirectionnelle, vous pouvez déclarer la contrainte NOT NULL sur le mapping <key> : ]]> D'un autre côté, si un enfant pouvait avoir plusieurs parent, une association plusieurs-vers-plusieurs est plus appropriée : ]]> Définitions des tables : Pour plus d'exemples et une revue complète du mapping de la relation parent/enfant, voir see . Des mappings d'association plus exotiques sont possibles, nous cataloguerons toutes les possibilités dans le prochain chapitre.