484 lines
20 KiB
XML
484 lines
20 KiB
XML
<?xml version="1.0" encoding="iso-8859-1"?>
|
|
<chapter id="inheritance">
|
|
<title>Mapping d'héritage de classe</title>
|
|
|
|
<sect1 id="inheritance-strategies" revision="3">
|
|
<title>Les trois stratégies</title>
|
|
|
|
<para>
|
|
Hibernate supporte les trois stratégies d'héritage de base :
|
|
</para>
|
|
|
|
<itemizedlist>
|
|
<listitem>
|
|
<para>
|
|
une table par hiérarchie de classe (table per class hierarchy)
|
|
</para>
|
|
</listitem>
|
|
<listitem>
|
|
<para>
|
|
une table par classe fille (table per subclass)
|
|
</para>
|
|
</listitem>
|
|
<listitem>
|
|
<para>
|
|
une table par classe concrète (table per concrete class)
|
|
</para>
|
|
</listitem>
|
|
</itemizedlist>
|
|
|
|
<para>
|
|
Hibernate supporte en plus une quatrièmestratégie, légèrement différente, qui supporte le polymorphisme :
|
|
</para>
|
|
|
|
<itemizedlist>
|
|
<listitem>
|
|
<para>
|
|
le polymorphisme implicite
|
|
</para>
|
|
</listitem>
|
|
</itemizedlist>
|
|
|
|
<para>
|
|
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 <literal><subclass></literal> et
|
|
<literal><joined-subclass></literal> et <literal><union-subclass></literal>
|
|
pour le même élément <literal><class></literal> 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 <literal><class></literal>, en combinant
|
|
les éléments <literal><subclass></literal> et <literal><join></literal> (voir dessous).
|
|
</para>
|
|
|
|
<para>
|
|
Il est possible de définir des mappings de <literal>subclass</literal>, <literal>union-subclass</literal>,
|
|
et <literal>joined-subclass</literal> dans des documents de mapping séparés, directement sous
|
|
<literal>hibernate-mapping</literal>. Ceci vous permet d'étendre une hiérarchie de classe juste en
|
|
ajoutant un nouveau fichier de mapping. Vous devez spécifier un attribut <literal>extends</literal>
|
|
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.
|
|
</para>
|
|
|
|
<programlisting><![CDATA[
|
|
<hibernate-mapping>
|
|
<subclass name="DomesticCat" extends="Cat" discriminator-value="D">
|
|
<property name="name" type="string"/>
|
|
</subclass>
|
|
</hibernate-mapping>]]></programlisting>
|
|
|
|
|
|
<sect2 id="inheritance-tableperclass" >
|
|
<title>Une table par hiérarchie de classe</title>
|
|
|
|
<para>
|
|
Supposons que nous ayons une interface <literal>Payment</literal>, implémentée
|
|
par <literal>CreditCardPayment</literal>, <literal>CashPayment</literal>,
|
|
<literal>ChequePayment</literal>. La stratégie une table par hiérarchie serait :
|
|
</para>
|
|
|
|
<programlisting><![CDATA[<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>]]></programlisting>
|
|
|
|
<para>
|
|
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 <literal>CCTYPE</literal>,
|
|
ne peuvent avoir de contrainte <literal>NOT NULL</literal>.
|
|
</para>
|
|
|
|
</sect2>
|
|
|
|
<sect2 id="inheritance-tablepersubclass">
|
|
<title>Une table par classe fille</title>
|
|
|
|
<para>
|
|
La stratégie une table par classe fille serait :
|
|
</para>
|
|
|
|
<programlisting><![CDATA[<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>]]></programlisting>
|
|
|
|
<para>
|
|
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).
|
|
</para>
|
|
|
|
</sect2>
|
|
|
|
<sect2 id="inheritance-tablepersubclass-discriminator" revision="2">
|
|
<title>Une table par classe fille, en utilisant un discriminant</title>
|
|
|
|
<para>
|
|
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 <literal><subclass></literal> et
|
|
<literal><join></literal>, comme suit :
|
|
</para>
|
|
|
|
<programlisting><![CDATA[<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>]]></programlisting>
|
|
|
|
<para>
|
|
La déclaration optionnelle <literal>fetch="select"</literal> indique à Hibernate
|
|
de ne pas récupérer les données de la classe fille <literal>ChequePayment</literal> par une jointure externe lors des requêtes sur la classe mère.
|
|
</para>
|
|
|
|
</sect2>
|
|
|
|
<sect2 id="inheritance-mixing-tableperclass-tablepersubclass">
|
|
<title>Mélange d'une table par hiérarchie de classe avec une table par classe fille</title>
|
|
|
|
<para>
|
|
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 :
|
|
</para>
|
|
|
|
<programlisting><![CDATA[<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>]]></programlisting>
|
|
|
|
<para>
|
|
Pour importe laquelle de ces stratégies, une association polymorphique vers la classe racine
|
|
<literal>Payment</literal> est mappée en utilisant <literal><many-to-one></literal>.
|
|
</para>
|
|
|
|
<programlisting><![CDATA[<many-to-one name="payment" column="PAYMENT_ID" class="Payment"/>]]></programlisting>
|
|
|
|
</sect2>
|
|
|
|
<sect2 id="inheritance-tableperconcrete" revision="2">
|
|
<title>Une table par classe concrète</title>
|
|
|
|
<para>
|
|
Il y a deux manières d'utiliser la stratégie d'une table par classe concrète. La première
|
|
est d'employer <literal><union-subclass></literal>.
|
|
</para>
|
|
|
|
<programlisting><![CDATA[<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>]]></programlisting>
|
|
|
|
<para>
|
|
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.
|
|
</para>
|
|
|
|
<para>
|
|
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.
|
|
</para>
|
|
|
|
<para>
|
|
Si votre classe mère est abstraite, mappez la avec <literal>abstract="true"</literal>.
|
|
Bien sûr, si elle n'est pas abstraite, une table supplémentaire (par défaut,
|
|
<literal>PAYMENT</literal> dans l'exemple ci-dessus) est requise pour contenir des instances
|
|
de la classe mère.
|
|
</para>
|
|
|
|
</sect2>
|
|
|
|
<sect2 id="inheritance-tableperconcreate-polymorphism">
|
|
<title>Une table par classe concrète, en utilisant le polymorphisme implicite</title>
|
|
|
|
<para>
|
|
Une approche alternative est l'emploi du polymorphisme implicite :
|
|
</para>
|
|
|
|
<programlisting><![CDATA[<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>]]></programlisting>
|
|
|
|
<para>
|
|
Notez que nulle part nous ne mentionnons l'interface <literal>Payment</literal> explicitement.
|
|
Notez aussi que des propriétés de <literal>Payment</literal> sont mappées dans
|
|
chaque classe fille. Si vous voulez éviter des duplications, considérez l'utilisation des
|
|
entités XML (cf. <literal>[ <!ENTITY allproperties SYSTEM "allproperties.xml"> ]</literal>
|
|
dans la déclaration du <literal>DOCTYPE</literal> et <literal>&allproperties;</literal> dans le mapping).
|
|
</para>
|
|
|
|
<para>
|
|
L'inconvénient de cette approche est qu'Hibernate ne génère pas d'<literal>UNION</literal>s SQL
|
|
lors de l'exécution des requêtes polymorphiques.
|
|
</para>
|
|
|
|
<para>
|
|
Pour cette stratégie de mapping, une association polymorphique pour <literal>Payment</literal>
|
|
est habituellement mappée en utilisant <literal><any></literal>.
|
|
</para>
|
|
|
|
<programlisting><![CDATA[<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>]]></programlisting>
|
|
|
|
</sect2>
|
|
|
|
<sect2 id="inheritace-mixingpolymorphism">
|
|
<title>Mélange du polymorphisme implicite avec d'autres mappings d'héritage</title>
|
|
|
|
<para>
|
|
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 <literal><class></literal> (et puisque
|
|
<literal>Payment</literal> 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 <literal>Payment</literal>).
|
|
</para>
|
|
|
|
<programlisting><![CDATA[<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>]]></programlisting>
|
|
|
|
<para>
|
|
Encore une fois, nous ne mentionnons pas explicitement <literal>Payment</literal>.
|
|
Si nous exécutons une requête sur l'interface <literal>Payment</literal> - par
|
|
exemple, <literal>from Payment</literal> - Hibernate retournera
|
|
automatiquement les instances de <literal>CreditCardPayment</literal>
|
|
(et ses classes filles puisqu'elles implémentent aussi <literal>Payment</literal>),
|
|
<literal>CashPayment</literal> et <literal>ChequePayment</literal> mais pas
|
|
les instances de <literal>NonelectronicTransaction</literal>.
|
|
</para>
|
|
|
|
</sect2>
|
|
|
|
</sect1>
|
|
|
|
<sect1 id="inheritance-limitations">
|
|
<title>Limitations</title>
|
|
|
|
<para>
|
|
Il y a certaines limitations à l'approche du "polymorphisme implicite"
|
|
pour la stratégie de mapping d'une table par classe concrète.
|
|
Il y a plutôt moins de limitations restrictives aux mappings <literal><union-subclass></literal>.
|
|
</para>
|
|
|
|
<para>
|
|
La table suivante montre les limitations des mappings d'une table par classe concrète, et du polymorphisme implicite, dans Hibernate.
|
|
</para>
|
|
|
|
<table frame="topbot">
|
|
<title>Caractéristiques du mapping d'héritage</title>
|
|
<tgroup cols='8' align='left' colsep='1' rowsep='1'>
|
|
<colspec colname='c1' colwidth="1*"/>
|
|
<colspec colname='c2' colwidth="1*"/>
|
|
<colspec colname='c3' colwidth="1*"/>
|
|
<colspec colname='c4' colwidth="1*"/>
|
|
<colspec colname='c5' colwidth="1*"/>
|
|
<colspec colname='c6' colwidth="1*"/>
|
|
<colspec colname='c7' colwidth="1*"/>
|
|
<colspec colname='c8' colwidth="1*"/>
|
|
<thead>
|
|
<row>
|
|
<entry>Stratégie d'héritage</entry>
|
|
<entry>many-to-one polymorphique</entry>
|
|
<entry>one-to-one polymorphique</entry>
|
|
<entry>one-to-many polymorphique</entry>
|
|
<entry>many-to-many polymorphique</entry>
|
|
<entry><literal>load()/get()</literal> polymorphique</entry>
|
|
<entry>Requêtes polymorphiques</entry>
|
|
<entry>Jointures polymorphiques</entry>
|
|
<entry>Récupération par jointure externe</entry>
|
|
</row>
|
|
</thead>
|
|
<tbody>
|
|
<row>
|
|
<entry>une table par hiérarchie de classe</entry>
|
|
<entry><literal><many-to-one></literal></entry>
|
|
<entry><literal><one-to-one></literal></entry>
|
|
<entry><literal><one-to-many></literal></entry>
|
|
<entry><literal><many-to-many></literal></entry>
|
|
<entry><literal>s.get(Payment.class, id)</literal></entry>
|
|
<entry><literal>from Payment p</literal></entry>
|
|
<entry><literal>from Order o join o.payment p</literal></entry>
|
|
<entry><emphasis>supportée</emphasis></entry>
|
|
</row>
|
|
<row>
|
|
<entry>une table par classe fille</entry>
|
|
<entry><literal><many-to-one></literal></entry>
|
|
<entry><literal><one-to-one></literal></entry>
|
|
<entry><literal><one-to-many></literal></entry>
|
|
<entry><literal><many-to-many></literal></entry>
|
|
<entry><literal>s.get(Payment.class, id)</literal></entry>
|
|
<entry><literal>from Payment p</literal></entry>
|
|
<entry><literal>from Order o join o.payment p</literal></entry>
|
|
<entry><emphasis>supportée</emphasis></entry>
|
|
</row>
|
|
<row>
|
|
<entry>une table par classe concrète (union-subclass)</entry>
|
|
<entry><literal><many-to-one></literal></entry>
|
|
<entry><literal><one-to-one></literal></entry>
|
|
<entry><literal><one-to-many></literal> (pour <literal>inverse="true"</literal> seulement)</entry>
|
|
<entry><literal><many-to-many></literal></entry>
|
|
<entry><literal>s.get(Payment.class, id)</literal></entry>
|
|
<entry><literal>from Payment p</literal></entry>
|
|
<entry><literal>from Order o join o.payment p</literal></entry>
|
|
<entry><emphasis>supportée</emphasis></entry>
|
|
</row>
|
|
<row>
|
|
<entry>une table par classe concrète (polymorphisme implicite)</entry>
|
|
<entry><literal><any></literal></entry>
|
|
<entry><emphasis>non supporté</emphasis></entry>
|
|
<entry><emphasis>non supporté</emphasis></entry>
|
|
<entry><literal><many-to-any></literal></entry>
|
|
<entry><literal>s.createCriteria(Payment.class).add( Restrictions.idEq(id) ).uniqueResult()</literal></entry>
|
|
<entry><literal>from Payment p</literal></entry>
|
|
<entry><emphasis>non supportées</emphasis></entry>
|
|
<entry><emphasis>non supportée</emphasis></entry>
|
|
</row>
|
|
</tbody>
|
|
</tgroup>
|
|
</table>
|
|
|
|
</sect1>
|
|
|
|
</chapter>
|