La notion de composant est réutilisée dans différents contextes et pour différents buts dans Hibernate.
Une composant est un objet contenu et qui est persisté comme un type de valeur, pas comme une entité. Le terme "composant" fait référence à la notion de composition en programmation Orientée Objet (pas aux composants architecturaux). Par exemple, vous pourriez modéliser une personne de cette façon :
public class Person { private java.util.Date birthday; private Name name; private String key; public String getKey() { return key; } private void setKey(String key) { this.key=key; } public java.util.Date getBirthday() { return birthday; } public void setBirthday(java.util.Date birthday) { this.birthday = birthday; } public Name getName() { return name; } public void setName(Name name) { this.name = name; } ...... ...... }
public class Name { char initial; String first; String last; public String getFirst() { return first; } void setFirst(String first) { this.first = first; } public String getLast() { return last; } void setLast(String last) { this.last = last; } public char getInitial() { return initial; } void setInitial(char initial) { this.initial = initial; } }
Name peut être persisté en tant que composant de Person. Notez que Name définit des méthodes getter/setter pour ses propriétés persistantes, mais n'a ni besoin de déclarer d'interface particulière, ni besoin d'une propriété d'identifiant.
Notre mapping hibernate ressemblera à :
<class name="eg.Person" table="person"> <id name="Key" column="pid" type="string"> <generator class="uuid.hex"/> </id> <property name="birthday" type="date"/> <component name="Name" class="eg.Name"> <!-- attribut class optionnel --> <property name="initial"/> <property name="first"/> <property name="last"/> </component> </class>
La table personne contient les colonnes pid, birthday, initial, first et last.
Comme tous les types de valeur, les composants ne supportent par les références partagées. La sémantique de la valeur nulle d'un composant est intrinsèque. Lorsque l'on recharge l'objet contenu, Hibernate considèrera que si toutes les colonnes du composant sont nulles, alors le composant dans son ensemble est nul. Ce comportement devrait être appriprié dans la plupart des cas.
Les propriétés d'un composant peuvent être de n'importe quel type Hibernate (collections, association plusieurs-vers-un, autres composants, etc). Les composants dans des composants ne devraient pas être considérés comme exotiques. Hibernate est fait pour supporter un modèle objet très fin.
L'élément <component> accepte un sous-élément <parent> qui mappe une propriété de la classe du composant vers une référence à l'entité contenant l'élément.
<class name="eg.Person" table="person"> <id name="Key" column="pid" type="string"> <generator class="uuid.hex"/> </id> <property name="birthday" type="date"/> <component name="Name" class="eg.Name"> <parent name="namedPerson"/> <!-- reference vers Person --> <property name="initial"/> <property name="first"/> <property name="last"/> </component> </class>
Les collections d'objets dépendants sont supportées (ex un tableau de type Name). Déclarez votre collection de composants en remplaçant la balise <element> par une balise <composite-element>.
<set name="someNames" table="some_names" lazy="true"> <key column="id"/> <composite-element class="eg.Name"> <!-- attribut class requis --> <property name="initial"/> <property name="first"/> <property name="last"/> </composite-element> </set>
Note : si vous utilisez un Set d'éléments composés, il est très important d'implémenter equals() et hashCode() correctement.
Les éléments composés peuvent eux-mêmes contenir des composants mais pas de collection. Si votre composant contient d'autres composants, utiliser la balise <nested-composite-element>. C'est un cas plutôt exotique - une collection de composants qui eux-mêmes ont des composants. Face à cette situation vous devriez vous demander si une association un-à-plusieurs n'est pas plus adaptée. Essayez de remodeler l'élément composé en une entité - mais notez que bien que le modèle Java restera identique, le modèle relationnel et la sémantique de persistance étant légèrement différents.
Notez qu'un mapping d'éléments composites ne supporte pas les propriétés nullables lorsque vous utilisez un <set>. Hibernate doit utiliser chaque valeur de colonnes pour identifier un enregistrement lorsqu'il supprime les objets (il n'y a pas de colonne séparée faisant office de clé primaire dans la table des éléments composites), et ce n'est pas possible avec des valeurs nulles. Vou devez donc soit vous limiter à des propriétés non-nulles, soit choisir <list>, <map>, <bag> ou <idbag> lors de vos mappings d'éléments composites.
Un cas particulier d'élément composite est un élément composite contenant un élément <many-to-one>. Un tel mapping vous permet de mapper les colonnes supplémentaires d'une table d'association plusieurs-vers-plusieurs et de les rendre accessibles dans la classe de l'élément composite. L'exemple suivant est une association plusieurs-vers-plusieurs entre une Order (commande) et un Item (article) où purchaseDate, price et quantity sont des propriétés de l'association :
<class name="eg.Order" .... > .... <set name="purchasedItems" table="purchase_items" lazy="true"> <key column="order_id"> <composite-element class="eg.Purchase"> <property name="purchaseDate"/> <property name="price"/> <property name="quantity"/> <many-to-one name="item" class="eg.Item"/> <!-- l'attribut classe est optionnel --> </composite-element> </set> </class>
De la même façon les associations ternaires (ou quaternaires, etc...) sont possibles :
<class name="eg.Order" .... > .... <set name="purchasedItems" table="purchase_items" lazy="true"> <key column="order_id"> <composite-element class="eg.OrderLine"> <many-to-one name="purchaseDetails class="eg.Purchase"/> <many-to-one name="item" class="eg.Item"/> </composite-element> </set> </class>
Les éléments composites peuvent faire partie des requêtes en utilisant la même syntaxe que celle utilisée pour les associations entre entités.
L'élément <composite-index> vous permet de mapper une classe composant en tant que clé d'une Map. Vérifier que vous avez bien surchargé hashCode() et equals() dans la classe composant.
Vous pouvez utiliser un composant comme identifiant d'une classe d'entité. Votre composant doit satisfaire certains critères :
Il doit implémenter java.io.Serializable.
Il doit réimplémenter equals() et hashCode() de manière consistante avec la notion d'égalité d'une clé composite dans la base de données.
Vous ne pouvez pas utiliser d'IdentifierGenerator pour générer de clés composées. L'application doit au contraire assigner ses propres identifiants
Dans la mesure ou un identifiant composé doit être assigné avant de pouvoir sauver l'objet, on ne peut pas utiliser la propriété unsaved-value de l'identifiant pour distinguer les instances nouvelles des instances sauvées dans une prédédente session.
Vous pouvez à la place implémenter Interceptor.isUnsaved() si vous souhaitez tout de même utiliser saveOrUpdate() ou la sauvegarde / mise à jour en cascade. Vous pouvez également positionner l'attribut unsaved-value sur l'élément <version> (ou <timestamp>) pour spécifier une valeur qui indique une nouvelle instance transiante. Dans ce cas, la version de l'entité est utilisée à la place de l'identifiant (assigné), et vous n'avez pas à implémenter vous-même Interceptor.isUnsaved().
Utilisez l'élément <composite-id> (mêmes attributs et éléments que <component>) au lieu de <id> pour la déclaration d'une classe identifiant composé :
<class name="eg.Foo" table"FOOS"> <composite-id name="compId" class="eg.FooCompositeID"> <key-property name="string"/> <key-property name="short"/> <key-property name="date" column="date_" type="date"/> </composite-id> <property name="name"/> .... </class>
En conséquence, chaque clé étrangère vers la table FOOS est aussi composée. Vous devez déclarez ceci dans les mappings de et vers les autres classes. Une association vers Foo serait déclarée comme :
<many-to-one name="foo" class="eg.Foo"> <!-- l'attribut class est optionnel, comme d'habitude --> <column name="foo_string"/> <column name="foo_short"/> <column name="foo_date"/> </many-to-one>
Le nouvel élément <column> est aussi utilisé par les types personnalisés à multiple colonnes. C'est une alternative à l'attribut column. Une collection avec des éléments de type Foo utiliserait :
<set name="foos"> <key column="owner_id"/> <many-to-many class="eg.Foo"> <column name="foo_string"/> <column name="foo_short"/> <column name="foo_date"/> </many-to-many> </set>
Comme d'habitude, <one-to-many>, ne déclare pas de colonne.
Si Foo contient lui même des collections, elles auront aussi besoin d'une clé étrangère composée.
<class name="eg.Foo"> .... .... <set name="dates" lazy="true"> <key> <!-- une collection hérite du type de clé composite --> <column name="foo_string"/> <column name="foo_short"/> <column name="foo_date"/> </key> <element column="foo_date" type="date"/> </set> </class>
Vous pouvez même mapper une propriété de type Map:
<dynamic-component name="userAttributes"> <property name="foo" column="FOO"/> <property name="bar" column="BAR"/> <many-to-one name="baz" class="eg.Baz" column="BAZ"/> </dynamic-component>
La définition d'un mapping de <dynamic-component> est identique à <component>. L'avantage de ce type de mapping est la possibilité de déterminer les propriétés réelles du bean au moment du déploiement, en éditant simplement le document de mapping (la manipulation à l'éxécution du document de mapping est aussi possible, via un parseur DOM).