404 lines
16 KiB
XML
404 lines
16 KiB
XML
<chapter id="components">
|
|
<title>Mapeo de Componentes</title>
|
|
|
|
<para>
|
|
La noción de un <emphasis>componente</emphasis> es reusada en muchos contextos diferentes,
|
|
para propósitos diferentes, a través de Hibernate.
|
|
</para>
|
|
|
|
<sect1 id="components-dependentobjects">
|
|
<title>Objetos dependientes</title>
|
|
|
|
<para>
|
|
Un componente es un objeto contenido que es persistido como un tipo de valor, no una
|
|
referencia de entidad. El término "componente" hace referencia a la noción orientada a
|
|
objetos de composición (no a componentes a nivel de arquitectura). Por ejemplo, podrías
|
|
modelar una persona como:
|
|
</para>
|
|
|
|
<programlisting><![CDATA[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;
|
|
}
|
|
......
|
|
......
|
|
}]]></programlisting>
|
|
|
|
<programlisting><![CDATA[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;
|
|
}
|
|
}]]></programlisting>
|
|
|
|
<para>
|
|
Ahora <literal>Name</literal> puede ser persistido como un componente de
|
|
<literal>Person</literal>. Observa que <literal>Name</literal> define métodos
|
|
getter y setter para sus propiedades persistentes, pero no necesita declarar
|
|
ninguna interface ni propiedades identificadoras.
|
|
</para>
|
|
|
|
<para>
|
|
Nuestro mapeo de Hibernate se vería así:
|
|
</para>
|
|
|
|
<programlisting><![CDATA[<class name="eg.Person" table="person">
|
|
<id name="Key" column="pid" type="string">
|
|
<generator class="uuid.hex"/>
|
|
</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>]]></programlisting>
|
|
|
|
<para>
|
|
La tabla person tendría las columnas <literal>pid</literal>,
|
|
<literal>birthday</literal>,
|
|
<literal>initial</literal>,
|
|
<literal>first</literal> y
|
|
<literal>last</literal>.
|
|
</para>
|
|
|
|
<para>
|
|
Como todos los tipos de valor, los componentes no soportan referencias compartidas.
|
|
En otras palabras, dos personas pueden tener el mismo nombre, pero los dos objetos
|
|
persona contendrían dos objetos nombre independientes, sólo "iguales" en valor.
|
|
La semántica de valor nulo de un componente es <emphasis>ad hoc</emphasis>.
|
|
Cuando se recargue el objeto contenedor, Hibernate asumirá que si todas las columnas del
|
|
componente son nulas, el componente entero es nulo. Esto debe estar bien para la mayoría
|
|
de propósitos.
|
|
</para>
|
|
|
|
<para>
|
|
Las propiedades de un componentes pueden ser de cualquier tipo de Hibernate
|
|
(colecciones, muchos-a-uno, asociaciones, otros componentes, etc). Los componentes
|
|
anidados <emphasis>no</emphasis> deben ser considerados un uso exótico. Hibernate está
|
|
concebido para soportar un modelo de objetos granularizado en fino.
|
|
</para>
|
|
|
|
<para>
|
|
El elemento <literal><component></literal> permite un subelemento
|
|
<literal><parent></literal> que mapee una propiedad de la clase del componente
|
|
como una referencia de regreso a la entidad contenedora.
|
|
</para>
|
|
|
|
<programlisting><![CDATA[<class name="eg.Person" table="person">
|
|
<id name="Key" column="pid" type="string">
|
|
<generator class="uuid.hex"/>
|
|
</id>
|
|
<property name="birthday" type="date"/>
|
|
<component name="Name" class="eg.Name" unique="true">
|
|
<parent name="namedPerson"/> <!-- reference back to the Person -->
|
|
<property name="initial"/>
|
|
<property name="first"/>
|
|
<property name="last"/>
|
|
</component>
|
|
</class>]]></programlisting>
|
|
|
|
</sect1>
|
|
|
|
<sect1 id="components-incollections" revision="1">
|
|
<title>Colecciones de objetos dependientes</title>
|
|
|
|
<para>
|
|
Las colecciones de componentes están soportadas (por ejemplo,
|
|
un array de tipo <literal>Name</literal>). Declara tu colección
|
|
de componentes remplazando la etiqueta <literal><element></literal>
|
|
por una etiqueta <literal><composite-element></literal>.
|
|
</para>
|
|
|
|
<programlisting><![CDATA[<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>]]></programlisting>
|
|
|
|
<para>
|
|
Nota: si defines un <literal>Set</literal> de elementos compuestos, es muy
|
|
importante implementar <literal>equals()</literal> y <literal>hashCode()</literal>
|
|
correctamente.
|
|
</para>
|
|
|
|
<para>
|
|
Los elementos compuestos pueden contener componentes pero no colecciones.
|
|
Si tu elemento compuesto contiene a su vez componentes, usa la etiqueta
|
|
<literal><nested-composite-element></literal>. Este es un caso bastante
|
|
exótico - una colección de componentes que a su vez tienen componentes. A esta
|
|
altura debes estar preguntándote si una asociación uno-a-muchos es más
|
|
apropiada. Intenta remodelar el elemento compuesto como una entidad - pero
|
|
observa que aunque el modelo Java es el mismo, el modelo relacional y la
|
|
semántica de persistencia siguen siendo ligeramente diferentes.
|
|
</para>
|
|
|
|
<para>
|
|
Por favor observa que un mapeo de elemento compuesto no soporta
|
|
propiedades nulables si estás usando un <literal><set></literal>.
|
|
Hibernate tiene que usar cada columna para identificar un registro
|
|
al borrar objetos (no hay una columna clave primaria separada en la tabla del
|
|
elemento compuesto), lo que es imposible con valores nulos. Tienes que, o bien usar
|
|
sólo propiedades no nulas en un elemento compuesto o elegir un
|
|
<literal><list></literal>, <literal><map></literal>,
|
|
<literal><bag></literal> o <literal><idbag></literal>.
|
|
</para>
|
|
|
|
<para>
|
|
Un caso especial de un elemento compuesto es un elemento compuesto con un
|
|
elemento anidado <literal><many-to-one></literal>. Un mapeo como este
|
|
te permite mapear columnas extra de una tabla de asociación muchos-a-muchos
|
|
a la clase del elemento compuesto. La siguiente es una asociación muchos-a-muchos
|
|
de <literal>Order</literal> a <literal>Item</literal> donde
|
|
<literal>purchaseDate</literal>, <literal>price</literal> y
|
|
<literal>quantity</literal> son propiedades de la asociación:
|
|
</para>
|
|
|
|
<programlisting><![CDATA[<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>]]></programlisting>
|
|
|
|
<para>
|
|
Por supuesto, no puede haber una referencia a la compra del otro lado para la
|
|
navegación bidireccional de la asociación. Recuerda que los componentes son tipos de
|
|
valor no permiten referencias compartidas. Una sola <literal>Purchase</literal> puede
|
|
estar en el conjunto de una <literal>Order</literal>, pero no puede ser referenciada
|
|
por el <literal>Item</literal> al mismo tiempo.
|
|
</para>
|
|
|
|
<para>Incluso son posibles las asociaciones ternarias (o cuaternarias, etc):</para>
|
|
|
|
<programlisting><![CDATA[<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>]]></programlisting>
|
|
|
|
<para>
|
|
Los elementos compuestos pueden aparecer en consultas usando la misma
|
|
sintáxis que las asociaciones a otras entidades.
|
|
</para>
|
|
|
|
</sect1>
|
|
|
|
<sect1 id="components-asmapindex">
|
|
<title>Componentes como índices de Map</title>
|
|
|
|
<para>
|
|
El elemento <literal><composite-map-key></literal> te permite mapear
|
|
una clase componente como la clave de un <literal>Map</literal>. Asegúrate que
|
|
sobrescribes <literal>hashCode()</literal> y <literal>equals()</literal>
|
|
correctamente en la clase componente.
|
|
</para>
|
|
</sect1>
|
|
|
|
<sect1 id="components-compositeid" revision="1">
|
|
<title>Componentes como identificadores compuestos</title>
|
|
|
|
<para>
|
|
Puedes usar un componente como un identidicador de una clase entidad. Tu clase
|
|
componente debe satisfacer ciertos requerimientos:
|
|
</para>
|
|
|
|
<itemizedlist spacing="compact">
|
|
<listitem>
|
|
<para>
|
|
Debe implementar <literal>java.io.Serializable</literal>.
|
|
</para>
|
|
</listitem>
|
|
<listitem>
|
|
<para>
|
|
Debe re-implementar <literal>equals()</literal> y
|
|
<literal>hashCode()</literal>, consistentemente con la
|
|
noción de base de datos de igualdad de clave compuesta.
|
|
</para>
|
|
</listitem>
|
|
</itemizedlist>
|
|
|
|
<para>
|
|
<emphasis>Nota: en Hibernat3, el segundo requerimiento no es absolutamente un
|
|
requerimiento rígido de Hibernate. Pero de todas formas, házlo.</emphasis>
|
|
</para>
|
|
|
|
<para>
|
|
No puedes usar un <literal>IdentifierGenerator</literal> para generar claves
|
|
compuestas. La aplicación debe, en cambio, asignar sus propios identificadores.
|
|
</para>
|
|
|
|
<para>
|
|
Usa la etiqueta <literal><composite-id></literal> (con elementos
|
|
anidados <literal><key-property></literal>) en lugar de la usual
|
|
declaración <literal><id></literal>. Por ejemplo, la clase
|
|
<literal>OrderLine</literal> tiene una clave primaria que depende de
|
|
la clave primaria (compuesta) de <literal>Order</literal>.
|
|
</para>
|
|
|
|
<programlisting><![CDATA[<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>]]></programlisting>
|
|
|
|
<para>
|
|
Ahora, cualquier clave foránea que referencie la tabla de <literal>OrderLine</literal>
|
|
es también compuesta. Debes declarar esto en tus mapeos de otras clases. Una asociación
|
|
a <literal>OrderLine</literal> sería mapeado así:
|
|
</para>
|
|
|
|
<programlisting><![CDATA[<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>]]></programlisting>
|
|
|
|
<para>
|
|
(Nota que la etiqueta <literal><column></literal> es una alternativa al
|
|
atributo <literal>column</literal> en cualquier sitio.)
|
|
</para>
|
|
|
|
<para>
|
|
Una asociación <literal>muchos-a-muchos</literal> a <literal>OrderLine</literal>
|
|
también usa la clave foránea compuesta:
|
|
</para>
|
|
|
|
<programlisting><![CDATA[<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>]]></programlisting>
|
|
|
|
<para>
|
|
La colección de <literal>OrderLine</literal>s en <literal>Order</literal> usaría:
|
|
</para>
|
|
|
|
<programlisting><![CDATA[<set name="orderLines" inverse="true">
|
|
<key>
|
|
<column name="orderId"/>
|
|
<column name="customerId"/>
|
|
</key>
|
|
<one-to-many class="OrderLine"/>
|
|
</set>]]></programlisting>
|
|
|
|
<para>
|
|
(El elemento <literal><one-to-many></literal>, como es usual, no declara columnas.)
|
|
</para>
|
|
|
|
<para>
|
|
Si <literal>OrderLine</literal> posee una colección por sí misma, tiene también
|
|
una clave foránea compuesta.
|
|
</para>
|
|
|
|
<programlisting><![CDATA[<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>]]></programlisting>
|
|
|
|
</sect1>
|
|
|
|
<sect1 id="components-dynamic" revision="1">
|
|
<title>Componentes dinámicos</title>
|
|
|
|
<para>
|
|
Puedes incluso mapear una propiedad de tipo <literal>Map</literal>:
|
|
</para>
|
|
|
|
<programlisting><![CDATA[<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>]]></programlisting>
|
|
|
|
<para>
|
|
La semántica de un mapeo <literal><dynamic-component></literal> es ídentica
|
|
a la de <literal><component></literal>. La ventaja de este tipo de mapeos es
|
|
la habilidad para determinar las propiedades reales del bean en tiempo de despliegue,
|
|
sólo con editar el documento de mapeo. La manipulación del documento de mapeo en tiempo
|
|
de ejecución es también posible, usando un analizador DOM. Incluso mejor, puedes acceder
|
|
(y cambiar) el metamodelo de tiempo de configuración de Hibernate por medio del objeto
|
|
<literal>Configuration</literal>.
|
|
</para>
|
|
|
|
</sect1>
|
|
|
|
</chapter>
|