hibernate-orm/doc/reference/fr/modules/inheritance_mapping.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>&lt;subclass&gt;</literal> et
<literal>&lt;joined-subclass&gt;</literal> et <literal>&lt;union-subclass&gt;</literal>
pour le même élément <literal>&lt;class&gt;</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>&lt;class&gt;</literal>, en combinant
les éléments <literal>&lt;subclass&gt;</literal> et <literal>&lt;join&gt;</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>&lt;subclass&gt;</literal> et
<literal>&lt;join&gt;</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>&lt;many-to-one&gt;</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>&lt;union-subclass&gt;</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>[ &lt;!ENTITY allproperties SYSTEM "allproperties.xml"&gt; ]</literal>
dans la déclaration du <literal>DOCTYPE</literal> et <literal>&amp;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>&lt;any&gt;</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>&lt;class&gt;</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>&lt;union-subclass&gt;</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>&lt;many-to-one&gt;</literal></entry>
<entry><literal>&lt;one-to-one&gt;</literal></entry>
<entry><literal>&lt;one-to-many&gt;</literal></entry>
<entry><literal>&lt;many-to-many&gt;</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>&lt;many-to-one&gt;</literal></entry>
<entry><literal>&lt;one-to-one&gt;</literal></entry>
<entry><literal>&lt;one-to-many&gt;</literal></entry>
<entry><literal>&lt;many-to-many&gt;</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>&lt;many-to-one&gt;</literal></entry>
<entry><literal>&lt;one-to-one&gt;</literal></entry>
<entry><literal>&lt;one-to-many&gt;</literal> (pour <literal>inverse="true"</literal> seulement)</entry>
<entry><literal>&lt;many-to-many&gt;</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>&lt;any&gt;</literal></entry>
<entry><emphasis>non supporté</emphasis></entry>
<entry><emphasis>non supporté</emphasis></entry>
<entry><literal>&lt;many-to-any&gt;</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>