La notion de composants est réutilisé dans différents contextes, avec différents objectifs, à travers Hibernate.
Le composant est un objet inclu dans un autre qui est sauvegardé comme une valeur, et non pas comme une entité. Le composant fait référence à la notion (au sens objet) de composition (et non pas de composant au sens d'architecture de composants). Par exemple on pourrait modélisé l'objet 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; } }
Maintenant Name peut-être sauvegardé comme un composant de Person. Remarquer que Name définit des methodes d'accès et de modification pour ses propriétés persistantes, mais il n'a pas besoin des interfaces ou des propriétés d'identification ( par exemple getId() ) qui sont propres aux entités.
Nous serions alors amené à mapper ce composant de cette façon:
<class name="eg.Person" table="person"> <id name="Key" column="pid" type="string"> <generator class="uuid"/> </id> <property name="birthday" type="date"/> <component name="Name" class="eg.Name"> <!-- class attribute optional --> <property name="initial"/> <property name="first"/> <property name="last"/> </component> </class>
La table person aurai les colonnes pid, birthday, initial, first and last.
Comme tous les types valeurs, les composants ne supportent pas les références partagés. En d'autres mots, deux instances de person peuvent avoir un même nom, mais ces noms sont indépendants, ils peuvent être identiques si on les compare par valeur mais ils représentent deux objets distincts en mémoire. La notion de nullité pour un composant est ad hoc. Quand il recharge l'objet qui contient le composant, Hibernate supposera que si tous les champs du composants sont nuls alors le composant sera positionné à la valeur null. Ce choix programmatif devrait être satisfaisant dans la plupart des cas.
Les propriétés d'un composant peuvent être de tous les types qu'Hibernate supporte habituellement (collections, many-to-one associations, autres composants, etc). Les composants inclus ne doivent pas être vus comme quelque chose d'exotique. Hibernate a été conçu pour supporter un modèle objet très granulaire.
Le <component> peut inclure dans la liste de ses propriétés une référence au <parent> conteneur.
<class name="eg.Person" table="person"> <id name="Key" column="pid" type="string"> <generator class="uuid"/> </id> <property name="birthday" type="date"/> <component name="Name" class="eg.Name" unique="true"> <parent name="namedPerson"/> <!-- référence arrière à Person --> <property name="initial"/> <property name="first"/> <property name="last"/> </component> </class>
Les collections d'objets dépendants sont supportés (exemple: un tableau de type Name). Déclarer la collection de composants en remplaçant le tag <element> par le tag <composite-element>.
<set name="someNames" table="some_names" lazy="true"> <key column="id"/> <composite-element class="eg.Name"> <!-- class attribute required --> <property name="initial"/> <property name="first"/> <property name="last"/> </composite-element> </set>
Remarque: Si vous définissez un Set d'élément composite, il est très important d'implémenter la méthode equals() et hashCode() correctement.
Les élements composite peuvent aussi contenir des composants mais pas des collections. Si votre élément composite contient aussi des composants, utilisez l'élément <nested-composite-element> . Une collections de composants qui ccontiennent eux-mêmes des composants est un cas très exotique. A ce stade demandez-vous si une association un-à-plusieurs ne serait pas plus approprié. Essayez de re remodeler votre élément composite comme une entité ( Dans ce cas même si le modèle Java est le même la logique de persitence et de relation sont tout de même différentes)
Remarque, le mapping d'éléments composites ne supporte pas la nullité des propriétés lorsqu'on utilise un <set>. Hibernate lorsqu'il supprime un objet utilise chaque colonne pour identifier un objet (on ne peut pas utiliser des clés primaires distinctes dans une table d'éléments composites), ce qui n'est pas possible avec des valeurs nulles. Vous devez donc choisir d'interdire la nullité des propriétés d'un élément composite ou choisir un autre type de collection comme : <list>, <map>, <bag> ou <idbag>.
Un cas particulier d'élément composite est un élément composite qui inclut un élément <many-to-one>. Un mapping comme celui-ci vous permet d'associer les colonnes d'une table d'association plusieurs à plusieurs (many-to-many) à la classse de l'élément composite. L'exemple suivant est une association plusieurs à plusieurs de Order à Item à 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"/> <!-- class attribute is optional --> </composite-element> </set> </class>
Bien sûr, il ne peut pas y avoir de référence à l'achat (purchase) depuis l'article (item), pour pouvoir naviguer de façon bidirectionnelle dans l'association. N'oubliez pas que les composants sont de type valeurs et n'autorise pas les références partagées.
Même les associations ternaires ou quaternaires 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 apparaître dans les requêtes en utilisant la même syntaxe que associations
l'élément <composite-map-key> vous permet d'utiliser une classe de composant comme indice de Map. Assurez-vous d'avoir surdéfini hashCode() et equals() dans la classe du composant.
Vous pouvez utiliser un composant comme identifiant d'une entité. Mais pour cela la classe du composant doit respecter certaines règles.
Elle doit implémenter java.io.Serializable.
Elle doit redéfinir equals() et hashCode(), de façon cohérente avec le fait qu'elle définit une clé composite dans la base de données.
Remarque: avec hibernate3, la seconde règle n'est plus absolument necessaire mais faîtes le quand même.
Vous ne pouvez pas utiliser de IdentifierGenerator pour générer une clé composite, l'application devra définir elle même ses propres identifiants.
Utiliser l'élément <composite-id> (en incluant l'élément <key-property>) à la place de l'habituel déclaration <id>. Par exemple la classe OrderLine qui dépend de la clé primaire (composite) de Order.
<class name="OrderLine"> <composite-id name="id" class="OrderLineId"> <key-property name="lineId"/> <key-property name="orderId"/> <key-property name="customerId"/> </composite-id> <property name="name"/> <many-to-one name="order" class="Order" insert="false" update="false"> <column name="orderId"/> <column name="customerId"/> </many-to-one> .... </class>
Maintenant toutes clés étrangères référençant la table OrderLine devra aussi être composite. Vous devez en tenir compte lorsque vous écrivez vos mapping d'association pour les autres classes. Une association à OrderLine devrait être mappé de la façon suivante :
<many-to-one name="orderLine" class="OrderLine"> <!-- the "class" attribute is optional, as usual --> <column name="lineId"/> <column name="orderId"/> <column name="customerId"/> </many-to-one>
(Remarque: l'élément <column> est une alternative à l'attribut column que l'on utilise partout.)
Une association plusieurs-à-plusieurs (many-to-many) à OrderLine utilisera aussi une clé étrangère composite:
<set name="undeliveredOrderLines"> <key column name="warehouseId"/> <many-to-many class="OrderLine"> <column name="lineId"/> <column name="orderId"/> <column name="customerId"/> </many-to-many> </set>
La collection des OrderLines dans Order utilisera:
<set name="orderLines" inverse="true"> <key> <column name="orderId"/> <column name="customerId"/> </key> <one-to-many class="OrderLine"/> </set>
(L'élément <one-to-many>, comme d'habitude, ne déclare pas de colonne.)
Si OrderLine lui-même possède une collection, celle-ci aura aussi une clé composite étrangère.
<class name="OrderLine"> .... .... <list name="deliveryAttempts"> <key> <!-- a collection inherits the composite key type --> <column name="lineId"/> <column name="orderId"/> <column name="customerId"/> </key> <list-index column="attemptId" base="1"/> <composite-element class="DeliveryAttempt"> ... </composite-element> </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="Baz" column="BAZ_ID"/> </dynamic-component>
La sémantique de l'association à un <dynamic-component> est identique à celle que l'on utilise pour les composants. L'avantage de ce type de mapping est qu'il pemet de déterminer les véritables propriétés du bean au moment su déploiement en éditant simplement le document de mapping. La manipulation du document de mapping pendant l'execution de l'application est aussi possible en utilisant un parser DOM. Il ya même mieux, vous pouvez accéder (et changer) le metamodel de configuration d'hibernate en utilisant l'objet Configuration