Hibernate supporte les trois stratégies d'héritage de base :
une table par hiérarchie de classe (table per class hierarchy)
table per subclass
une table par classe concrète (table per concrete class)
Hibernate supporte en plus une quatrièmestratégie, légèrement différente, qui supporte le polymorphisme :
le polymorphisme implicite
Il est possible d'utiliser différentes stratégies de mapping pour différentes branches d'une même hiérarchie d'héritage, et
alors d'employer le polymorphisme implicite pour réaliser le polymorphisme à travers toute la hiérarchie. Pourtant, Hibernate
ne supporte pas de mélanger des mappings <subclass>
et <joined-subclass>
et <union-subclass>
pour le même élément <class>
racine. Il est possible de mélanger ensemble les stratégies d'une table par hiérarchie et d'une table par sous-classe, pour
le même élément <class>
, en combinant les éléments <subclass>
et <join>
(voir dessous).
Il est possible de définir des mappings de subclass
, union-subclass
, et joined-subclass
dans des documents de mapping séparés, directement sous hibernate-mapping
. Ceci vous permet d'étendre une hiérarchie de classe juste en ajoutant un nouveau fichier de mapping. Vous devez spécifier
un attribut extends
dans le mapping de la sous-classe, en nommant une super-classe précédemment mappée. Note : précédemment cette foncionnalité
rendait l'ordre des documents de mapping important. Depuis Hibernate3, l'ordre des fichier de mapping n'importe plus lors
de l'utilisation du mot-clef "extends". L'ordre à l'intérieur d'un simple fichier de mapping impose encore de définir les
classes mères avant les classes filles.
<hibernate-mapping> <subclass name="DomesticCat" extends="Cat" discriminator-value="D"> <property name="name" type="string"/> </subclass> </hibernate-mapping>
Supposons que nous ayons une interface Payment
, implémentée par CreditCardPayment
, CashPayment
, ChequePayment
. La stratégie une table par hiérarchie serait :
<class name="Payment" table="PAYMENT"> <id name="id" type="long" column="PAYMENT_ID"> <generator class="native"/> </id> <discriminator column="PAYMENT_TYPE" type="string"/> <property name="amount" column="AMOUNT"/> ... <subclass name="CreditCardPayment" discriminator-value="CREDIT"> <property name="creditCardType" column="CCTYPE"/> ... </subclass> <subclass name="CashPayment" discriminator-value="CASH"> ... </subclass> <subclass name="ChequePayment" discriminator-value="CHEQUE"> ... </subclass> </class>
Une seule table est requise. Une grande limitation de cette stratégie est que les colonnes déclarées par les classes filles,
telles que CCTYPE
, ne peuvent avoir de contrainte NOT NULL
.
La stratégie une table par classe fille serait :
<class name="Payment" table="PAYMENT"> <id name="id" type="long" column="PAYMENT_ID"> <generator class="native"/> </id> <property name="amount" column="AMOUNT"/> ... <joined-subclass name="CreditCardPayment" table="CREDIT_PAYMENT"> <key column="PAYMENT_ID"/> <property name="creditCardType" column="CCTYPE"/> ... </joined-subclass> <joined-subclass name="CashPayment" table="CASH_PAYMENT"> <key column="PAYMENT_ID"/> ... </joined-subclass> <joined-subclass name="ChequePayment" table="CHEQUE_PAYMENT"> <key column="PAYMENT_ID"/> ... </joined-subclass> </class>
Quatre tables sont requises. Les trois tables des classes filles ont une clé primaire associée à la table classe mère (le modèle relationnel est une association un-vers-un).
Notez que l'implémentation Hibernate de la stratégie un table par classe fille ne nécessite pas de colonne discriminante dans
la table classe mère. D'autres implémentations de mappers Objet/Relationnel utilisent une autre implémentation de la stratégie
une table par classe fille qui nécessite une colonne de type discriminant dans la table de la classe mère. L'approche prise
par Hibernate est plus difficile à implémenter mais plus correcte d'une point de vue relationnel. Si vous aimeriez utiliser
une colonne discriminante avec la stratégie d'une table par classe fille, vous pourriez combiner l'utilisation de <subclass>
et <join>
, comme suit :
<class name="Payment" table="PAYMENT"> <id name="id" type="long" column="PAYMENT_ID"> <generator class="native"/> </id> <discriminator column="PAYMENT_TYPE" type="string"/> <property name="amount" column="AMOUNT"/> ... <subclass name="CreditCardPayment" discriminator-value="CREDIT"> <join table="CREDIT_PAYMENT"> <key column="PAYMENT_ID"/> <property name="creditCardType" column="CCTYPE"/> ... </join> </subclass> <subclass name="CashPayment" discriminator-value="CASH"> <join table="CASH_PAYMENT"> <key column="PAYMENT_ID"/> ... </join> </subclass> <subclass name="ChequePayment" discriminator-value="CHEQUE"> <join table="CHEQUE_PAYMENT" fetch="select"> <key column="PAYMENT_ID"/> ... </join> </subclass> </class>
La déclaration optionnelle fetch="select"
indique à Hibernate de ne pas récupérer les données de la classe fille ChequePayment
par une jointure externe lors des requêtes sur la classe mère.
Vous pouvez même mélanger les stratégies d'une table par hiérarchie de classe et d'une table par classe fille en utilisant cette approche :
<class name="Payment" table="PAYMENT"> <id name="id" type="long" column="PAYMENT_ID"> <generator class="native"/> </id> <discriminator column="PAYMENT_TYPE" type="string"/> <property name="amount" column="AMOUNT"/> ... <subclass name="CreditCardPayment" discriminator-value="CREDIT"> <join table="CREDIT_PAYMENT"> <property name="creditCardType" column="CCTYPE"/> ... </join> </subclass> <subclass name="CashPayment" discriminator-value="CASH"> ... </subclass> <subclass name="ChequePayment" discriminator-value="CHEQUE"> ... </subclass> </class>
Pour importe laquelle de ces stratégies, une association polymorphique vers la classe racine Payment
est mappée en utilisant <many-to-one>
.
<many-to-one name="payment" column="PAYMENT_ID" class="Payment"/>
Il y a deux manières d'utiliser la stratégie d'une table par classe concrète. La première est d'employer <union-subclass>
.
<class name="Payment"> <id name="id" type="long" column="PAYMENT_ID"> <generator class="sequence"/> </id> <property name="amount" column="AMOUNT"/> ... <union-subclass name="CreditCardPayment" table="CREDIT_PAYMENT"> <property name="creditCardType" column="CCTYPE"/> ... </union-subclass> <union-subclass name="CashPayment" table="CASH_PAYMENT"> ... </union-subclass> <union-subclass name="ChequePayment" table="CHEQUE_PAYMENT"> ... </union-subclass> </class>
Trois tables sont nécessaires pour les classes filles. Chaque table définit des colonnes pour toutes les propriétés de la classe, incluant les propriétés héritéés.
La limitation de cette approche est que si une propriété est mappée sur la classe mère, le nom de la colonne doit être le même pour toutes les classes filles. (Nous pourrions être plus souple dans une future version d'Hibernate). La stratégie du générateur d'identifiant n'est pas permise dans l'héritage de classes filles par union, en effet la valeur (NdT : seed) de la clef primaire doit être partagée par toutes les classes filles "union" d'une hiérarchie.
Si votre classe mère est abstraite, mappez la avec abstract="true"
. Bien sûr, si elle n'est pas abstraite, une table supplémentaire (par défaut, PAYMENT
dans l'exemple ci-dessus) est requise pour contenir des instances de la classe mère.
Une approche alternative est l'emploi du polymorphisme implicite :
<class name="CreditCardPayment" table="CREDIT_PAYMENT"> <id name="id" type="long" column="CREDIT_PAYMENT_ID"> <generator class="native"/> </id> <property name="amount" column="CREDIT_AMOUNT"/> ... </class> <class name="CashPayment" table="CASH_PAYMENT"> <id name="id" type="long" column="CASH_PAYMENT_ID"> <generator class="native"/> </id> <property name="amount" column="CASH_AMOUNT"/> ... </class> <class name="ChequePayment" table="CHEQUE_PAYMENT"> <id name="id" type="long" column="CHEQUE_PAYMENT_ID"> <generator class="native"/> </id> <property name="amount" column="CHEQUE_AMOUNT"/> ... </class>
Notez que nulle part nous ne mentionnons l'interface Payment
explicitement. Notez aussi que des propriétés de Payment
sont mappées dans chaque classe fille. Si vous voulez éviter des duplications, considérez l'utilisation des entités XML (cf.
[ <!ENTITY allproperties SYSTEM "allproperties.xml"> ]
dans la déclaration du DOCTYPE
et &allproperties;
dans le mapping).
L'inconvénient de cette approche est qu'Hibernate ne génère pas d'UNION
s SQL lors de l'exécution des requêtes polymorphiques.
Pour cette stratégie de mapping, une association polymorphique pour Payment
est habituellement mappée en utilisant <any>
.
<any name="payment" meta-type="string" id-type="long"> <meta-value value="CREDIT" class="CreditCardPayment"/> <meta-value value="CASH" class="CashPayment"/> <meta-value value="CHEQUE" class="ChequePayment"/> <column name="PAYMENT_CLASS"/> <column name="PAYMENT_ID"/> </any>
Il y a une chose supplémentaire à noter à propos de ce mapping. Puisque les classes filles sont chacune mappées avec leur
propre élément <class>
(et puisque Payment
est juste une interface), chaque classe fille pourrait facilement faire partie d'une autre hiérarchie d'héritage ! (Et vous
pouvez encore faire des requêtes polymorphiques pour l'interface Payment
).
<class name="CreditCardPayment" table="CREDIT_PAYMENT"> <id name="id" type="long" column="CREDIT_PAYMENT_ID"> <generator class="native"/> </id> <discriminator column="CREDIT_CARD" type="string"/> <property name="amount" column="CREDIT_AMOUNT"/> ... <subclass name="MasterCardPayment" discriminator-value="MDC"/> <subclass name="VisaPayment" discriminator-value="VISA"/> </class> <class name="NonelectronicTransaction" table="NONELECTRONIC_TXN"> <id name="id" type="long" column="TXN_ID"> <generator class="native"/> </id> ... <joined-subclass name="CashPayment" table="CASH_PAYMENT"> <key column="PAYMENT_ID"/> <property name="amount" column="CASH_AMOUNT"/> ... </joined-subclass> <joined-subclass name="ChequePayment" table="CHEQUE_PAYMENT"> <key column="PAYMENT_ID"/> <property name="amount" column="CHEQUE_AMOUNT"/> ... </joined-subclass> </class>
Encore une fois, nous ne mentionnons pas explicitement Payment
. Si nous exécutons une requête sur l'interface Payment
- par exemple, from Payment
- Hibernate retournera automatiquement les instances de CreditCardPayment
(et ses classes filles puisqu'elles implémentent aussi Payment
), CashPayment
et ChequePayment
mais pas les instances de NonelectronicTransaction
.