465 lines
19 KiB
XML
465 lines
19 KiB
XML
|
<chapter id="inheritance">
|
||
|
<title>Mapeo de Herencia</title>
|
||
|
|
||
|
<sect1 id="inheritance-strategies" revision="2">
|
||
|
<title>Las Tres Estrategias</title>
|
||
|
|
||
|
<para>
|
||
|
Hibernate soporta las tres estrategias básicas de mapeo de herencia:
|
||
|
</para>
|
||
|
|
||
|
<itemizedlist>
|
||
|
<listitem>
|
||
|
<para>
|
||
|
tabla por jerarquía de clases
|
||
|
</para>
|
||
|
</listitem>
|
||
|
<listitem>
|
||
|
<para>
|
||
|
tabla por subclase
|
||
|
</para>
|
||
|
</listitem>
|
||
|
<listitem>
|
||
|
<para>
|
||
|
tabla por clase concreta
|
||
|
</para>
|
||
|
</listitem>
|
||
|
</itemizedlist>
|
||
|
|
||
|
<para>
|
||
|
En adición, Hibernate soporta un cuarto, ligeramente diferente tipo
|
||
|
de polimorfismo:
|
||
|
</para>
|
||
|
|
||
|
<itemizedlist>
|
||
|
<listitem>
|
||
|
<para>
|
||
|
polimorfismo implícito
|
||
|
</para>
|
||
|
</listitem>
|
||
|
</itemizedlist>
|
||
|
|
||
|
<para>
|
||
|
Es posible usar estrategias de mapeo diferentes para diferentes
|
||
|
ramificaciones de la misma jerarquía de herencia, y entonces usar
|
||
|
polimorfismo implícito para conseguir polimorfismo a través de
|
||
|
toda la jerarquía. Sin embargo, Hibernate no soporta la mezcla de
|
||
|
mapeos <literal><subclass></literal>,
|
||
|
y <literal><joined-subclass></literal>
|
||
|
y <literal><union-subclass></literal> bajo el mismo elemento
|
||
|
<literal><class></literal> raíz. Es posible mezclar juntas las
|
||
|
estrategias de tabla por jerarquía y tabla por subclase, bajo el mismo
|
||
|
elemento <literal><class></literal>, combinando los elementos
|
||
|
<literal><subclass></literal> y <literal><join></literal>
|
||
|
(ver debajo).
|
||
|
</para>
|
||
|
|
||
|
<sect2 id="inheritance-tableperclass" >
|
||
|
<title>Tabla por jerarquía de clases</title>
|
||
|
|
||
|
<para>
|
||
|
Supón que tenemos una interface <literal>Payment</literal>, con
|
||
|
los implementadores <literal>CreditCardPayment</literal>,
|
||
|
<literal>CashPayment</literal>, <literal>ChequePayment</literal>.
|
||
|
El mapeo de tabla por jerarquía se vería así:
|
||
|
</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>
|
||
|
Se requiere exactamente una tabla. Hay una gran limitación de esta estrategia de mapeo:
|
||
|
las columnas declaradas por las subclases, como <literal>CCTYPE</literal>, no pueden
|
||
|
tener restricciones <literal>NOT NULL</literal>.
|
||
|
</para>
|
||
|
|
||
|
</sect2>
|
||
|
|
||
|
<sect2 id="inheritance-tablepersubclass">
|
||
|
<title>Tabla por subclase</title>
|
||
|
|
||
|
<para>
|
||
|
Un mapeo de tabla por sublclase se vería así:
|
||
|
</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>
|
||
|
Se requieren cuatro tablas. Las tres tablas de subclase tienen
|
||
|
asociaciones de clave primaria a la tabla de superclase (de modo
|
||
|
que en el modelo relacional es realmente una asociación uno-a-uno).
|
||
|
</para>
|
||
|
|
||
|
</sect2>
|
||
|
|
||
|
<sect2 id="inheritance-tablepersubclass-discriminator" revision="2">
|
||
|
<title>Tabla por subclase, usando un discriminador</title>
|
||
|
|
||
|
<para>
|
||
|
Observa que la implementación de Hibernate de tabla por subclase
|
||
|
no requiere ninguna columna discriminadora. Otros mapeadores
|
||
|
objeto/relacional usan una implementación diferente de tabla por
|
||
|
subclase que requiere una columna discriminadora de tipo en la tabla
|
||
|
de superclase. Este enfoque es mucho más difícil de implementar
|
||
|
pero discutiblemente más correcto desde un punto de vista relacional.
|
||
|
Si quisieras usar una columna discriminadora con la estrategia de
|
||
|
tabla por subclase, puedes combinar el uso de <literal><subclass></literal>
|
||
|
y <literal><join></literal>, como sigue:
|
||
|
</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 declaración opcional <literal>fetch="select"</literal> dice a Hibernate
|
||
|
que no recupere los datos de la subclase <literal>ChequePayment</literal>
|
||
|
usando una unión externa (outer join) al consultar la superclase.
|
||
|
</para>
|
||
|
|
||
|
</sect2>
|
||
|
|
||
|
<sect2 id="inheritance-mixing-tableperclass-tablepersubclass">
|
||
|
<title>Mezclando tabla por jerarquía de clases con tabla por subclase</title>
|
||
|
|
||
|
<para>
|
||
|
Puedes incluso mezclar las estrategias de tabla po jerarquía y tabla por
|
||
|
subclase usando este enfoque:
|
||
|
</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>
|
||
|
Para cualquiera de estas estrategias de mapeo, una asociación polimórfica
|
||
|
a la clase raíz <literal>Payment</literal> es mapeada usando <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="1">
|
||
|
<title>Tabla por clase concreta</title>
|
||
|
|
||
|
<para>
|
||
|
Podríamos ir de dos maneras a la estrategia de mapeo de tabla por clase
|
||
|
concreta. La primera es usar <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>
|
||
|
Están implicadas tres tablas. Cada tabla define columnas para todas las
|
||
|
propiedades de la clase, inccluyendo las propiedades heredadas.
|
||
|
</para>
|
||
|
|
||
|
<para>
|
||
|
La limitación de este enfoque es que si una propiedad es mapeada en la
|
||
|
superclase, el nombre de columna debe ser el mismo en todas las tablas
|
||
|
de subclase. (Podríamos relajar esto en un lanzamiento futuro de Hibernate.)
|
||
|
La estrategia de generador de indentidad no está permitida en la herencia
|
||
|
de unión de subclase, de hecho la semilla de clave primaria tiene que ser
|
||
|
compartida a través de todas las subclases unidas de una jerarquía.
|
||
|
</para>
|
||
|
|
||
|
</sect2>
|
||
|
|
||
|
<sect2 id="inheritance-tableperconcreate-polymorphism">
|
||
|
<title>Tabla por clase concreta, usando polimorfismo implícito</title>
|
||
|
|
||
|
<para>
|
||
|
Un enfoque alternativo es hacer uso de polimorfismo implícito:
|
||
|
</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>
|
||
|
Nota que en ningún sitio mencionamos la interface <literal>Payment</literal>
|
||
|
explícitamente. Nota además que las propiedades de <literal>Payment</literal>
|
||
|
son mapeadas en cada una de las subclases. Si quieres evitar duplicación,
|
||
|
considera usar entidades XML. (por ejemplo,
|
||
|
<literal>[ <!ENTITY allproperties SYSTEM "allproperties.xml"> ]</literal>
|
||
|
en la declaración <literal>DOCTYPE</literal> y <literal>&allproperties;</literal>
|
||
|
en el mapeo).
|
||
|
</para>
|
||
|
|
||
|
<para>
|
||
|
La desventaja de este enfoque es que Hibernate no genera <literal>UNION</literal>s
|
||
|
de SQL al realizar consultas polimórficas.
|
||
|
</para>
|
||
|
|
||
|
<para>
|
||
|
Para esta estrategia de mapeo, una asociación polimórfica a <literal>Payment</literal>
|
||
|
es mapeada generalmente usando <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>Mezclando polimorfismo implícito con otros mapeos de herencia</title>
|
||
|
|
||
|
<para>
|
||
|
Hay una cosa más por notar acerca de este mapeo. Ya que las subclases se mapean
|
||
|
cada una en su propio elemento <literal><class></literal> (y ya que
|
||
|
<literal>Payment</literal> es sólo una interface), cada una de las subclases
|
||
|
podría ser parte de otra jerarquía de herencia! (Y todavía puedes seguir usando
|
||
|
consultas polimórficas contra la 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>
|
||
|
Una vez más, no mencionamos a <literal>Payment</literal> explícitamente.
|
||
|
Si ejecutamos una consulta contra la interface <literal>Payment</literal>
|
||
|
- por ejemplo, <literal>from Payment</literal> - Hibernate devuelve
|
||
|
automáticamente instancias de <literal>CreditCardPayment</literal>
|
||
|
(y sus subclases, ya que ellas también implementan <literal>Payment</literal>),
|
||
|
<literal>CashPayment</literal> y <literal>ChequePayment</literal> pero
|
||
|
no instancias de <literal>NonelectronicTransaction</literal>.
|
||
|
</para>
|
||
|
|
||
|
</sect2>
|
||
|
|
||
|
</sect1>
|
||
|
|
||
|
<sect1 id="inheritance-limitations">
|
||
|
<title>Limitaciones</title>
|
||
|
|
||
|
<para>
|
||
|
Existen ciertas limitaciones al enfoque de "polimorfismo implícito" en
|
||
|
la estrategia de mapeo de tabla por clase concreta. Existen limitaciones
|
||
|
algo menos restrictivas a los mapeos <literal><union-subclass></literal>.
|
||
|
</para>
|
||
|
|
||
|
<para>
|
||
|
La siguiente tabla muestra las limitaciones de mapeos de tabla por
|
||
|
clase concreta, y de polmorfismo implícito, en Hibernate.
|
||
|
</para>
|
||
|
|
||
|
<table frame="topbot">
|
||
|
<title>Funcionalidades de mapeo de herencia</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>Estrategia de herencia</entry>
|
||
|
<entry>muchos-a-uno polimórfica</entry>
|
||
|
<entry>uno-a-uno polimórfica</entry>
|
||
|
<entry>uno-a-muchos polimórfica</entry>
|
||
|
<entry>mushos-a-muchos polimórfica</entry>
|
||
|
<entry><literal>load()/get()</literal> polimórficos</entry>
|
||
|
<entry>Consultas polimórficas</entry>
|
||
|
<entry>Uniones polimórficas</entry>
|
||
|
<entry>Recuperación por unión externa (outer join)</entry>
|
||
|
</row>
|
||
|
</thead>
|
||
|
<tbody>
|
||
|
<row>
|
||
|
<entry>tabla por jerarquía de clases</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>soportada</emphasis></entry>
|
||
|
</row>
|
||
|
<row>
|
||
|
<entry>tabla por subclase</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>soportada</emphasis></entry>
|
||
|
</row>
|
||
|
<row>
|
||
|
<entry>tabla por clase concreta (union-subclass)</entry>
|
||
|
<entry><literal><many-to-one></literal></entry>
|
||
|
<entry><literal><one-to-one></literal></entry>
|
||
|
<entry><literal><one-to-many></literal> (para <literal>inverse="true"</literal> solamente)</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>soportada</emphasis></entry>
|
||
|
</row>
|
||
|
<row>
|
||
|
<entry>tabla por clase concreta (polimorfismo implícito)</entry>
|
||
|
<entry><literal><any></literal></entry>
|
||
|
<entry><emphasis>no soportada</emphasis></entry>
|
||
|
<entry><emphasis>no soportada</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>no suportadas</emphasis></entry>
|
||
|
<entry><emphasis>no soportada</emphasis></entry>
|
||
|
</row>
|
||
|
</tbody>
|
||
|
</tgroup>
|
||
|
</table>
|
||
|
|
||
|
</sect1>
|
||
|
|
||
|
</chapter>
|