1174 lines
49 KiB
XML
1174 lines
49 KiB
XML
<chapter id="collections">
|
|
<title>Mapeo de Colecciones</title>
|
|
|
|
<sect1 id="collections-persistent" revision="3">
|
|
<title>Colecciones persistentes</title>
|
|
|
|
<para>
|
|
Hibernate requiere que los campos valuados en colección
|
|
persistentes sean declarados como un tipo de interface, por ejemplo:
|
|
</para>
|
|
|
|
<programlisting><![CDATA[public class Product {
|
|
private String serialNumber;
|
|
private Set parts = new HashSet();
|
|
|
|
public Set getParts() { return parts; }
|
|
void setParts(Set parts) { this.parts = parts; }
|
|
public String getSerialNumber() { return serialNumber; }
|
|
void setSerialNumber(String sn) { serialNumber = sn; }
|
|
}]]></programlisting>
|
|
|
|
<para>
|
|
La interface real podría ser <literal>java.util.Set</literal>,
|
|
<literal>java.util.Collection</literal>, <literal>java.util.List</literal>,
|
|
<literal>java.util.Map</literal>, <literal>java.util.SortedSet</literal>,
|
|
<literal>java.util.SortedMap</literal> o ... lo que te guste!
|
|
(Donde "lo que te guste" significa que tendrás que escribir una
|
|
implementación de <literal>org.hibernate.usertype.UserCollectionType</literal>.)
|
|
</para>
|
|
|
|
<para>
|
|
Nota cómo hemos inicializado la variable de instancia de
|
|
<literal>HashSet</literal>. Esta es la mejor forma de inicializar
|
|
propiedades valuadas en colección de instancias recién
|
|
instanciadas (no persistentes). Cuando haces persistente la instancia
|
|
- llamando a <literal>persist()</literal>, por ejemplo - Hibernate realmente
|
|
remplazará el <literal>HashSet</literal> con una instancia de una
|
|
implementación de <literal>Set</literal> propia de Hibernate.
|
|
Observa errores como este:
|
|
</para>
|
|
|
|
<programlisting><![CDATA[Cat cat = new DomesticCat();
|
|
Cat kitten = new DomesticCat();
|
|
....
|
|
Set kittens = new HashSet();
|
|
kittens.add(kitten);
|
|
cat.setKittens(kittens);
|
|
session.persist(cat);
|
|
kittens = cat.getKittens(); // Okay, kittens collection is a Set
|
|
(HashSet) cat.getKittens(); // Error!]]></programlisting>
|
|
|
|
<para>
|
|
Las colecciones persistentes inyectadas por Hibernate se comportan
|
|
como <literal>HashMap</literal>, <literal>HashSet</literal>,
|
|
<literal>TreeMap</literal>, <literal>TreeSet</literal> o
|
|
<literal>ArrayList</literal>, dependiendo del tipo de interface.
|
|
</para>
|
|
|
|
<para>
|
|
Las instancias de colecciones tienen el comportamiento usual de tipos de valor.
|
|
Son automáticamente persistidas al ser referenciadas por un objeto
|
|
persistente y automáticamente borradas al desreferenciarse. Si una
|
|
colección es pasada de un objeto persistente a otro, sus elementos
|
|
serían movidos de una tabla a otra. Dos entidades pueden no
|
|
compartir una referencia a la misma instancia de colección.
|
|
Debido al modelo relacional subyacente, las propiedades valuadas en
|
|
colección no soportan la semántica de valor nulo. Hibernate no
|
|
distingue entre una referencia de colección nula y una colección
|
|
vacía.
|
|
</para>
|
|
|
|
<para>
|
|
No debes tener que preocuparte demasiado por esto. Usa las colecciones
|
|
persistentes de la misma forma en que usas colecciones de Java ordinarias.
|
|
Sólo asegúrate que entiendes la semántica de las asociaciones
|
|
bidireccionales (discutida luego).
|
|
</para>
|
|
|
|
</sect1>
|
|
|
|
<sect1 id="collections-mapping" revision="2">
|
|
<title>Mapeos de colección</title>
|
|
|
|
<para>
|
|
El elemento de mapeo de Hibernate usado para mapear una colección
|
|
depende del tipo de la interface. Por ejemplom un elemento
|
|
<literal><set></literal> se usa para mapear propiedades de tipo
|
|
<literal>Set</literal>.
|
|
</para>
|
|
|
|
<programlisting><![CDATA[<class name="Product">
|
|
<id name="serialNumber" column="productSerialNumber"/>
|
|
<set name="parts">
|
|
<key column="productSerialNumber" not-null="true"/>
|
|
<one-to-many class="Part"/>
|
|
</set>
|
|
</class>]]></programlisting>
|
|
|
|
<para>
|
|
Aparte de <literal><set></literal>, existen además
|
|
los elementos de mapeo <literal><list></literal>,
|
|
<literal><map></literal>, <literal><bag></literal>,
|
|
<literal><array></literal> y <literal><primitive-array></literal>.
|
|
El elemento <literal><map></literal> es representativo:
|
|
</para>
|
|
|
|
<programlistingco>
|
|
<areaspec>
|
|
<area id="mappingcollection1" coords="2 65"/>
|
|
<area id="mappingcollection2" coords="3 65"/>
|
|
<area id="mappingcollection3" coords="4 65"/>
|
|
<area id="mappingcollection4" coords="5 65"/>
|
|
<area id="mappingcollection5" coords="6 65"/>
|
|
<area id="mappingcollection6" coords="7 65"/>
|
|
<area id="mappingcollection7" coords="8 65"/>
|
|
<area id="mappingcollection8" coords="9 65"/>
|
|
<area id="mappingcollection9" coords="10 65"/>
|
|
<area id="mappingcollection10" coords="11 65"/>
|
|
<area id="mappingcollection11" coords="12 65"/>
|
|
<area id="mappingcollection12" coords="13 65"/>
|
|
<area id="mappingcollection13" coords="14 65"/>
|
|
</areaspec>
|
|
<programlisting><![CDATA[<map
|
|
name="propertyName"
|
|
table="table_name"
|
|
schema="schema_name"
|
|
lazy="true|false"
|
|
inverse="true|false"
|
|
cascade="all|none|save-update|delete|all-delete-orphan"
|
|
sort="unsorted|natural|comparatorClass"
|
|
order-by="column_name asc|desc"
|
|
where="arbitrary sql where condition"
|
|
fetch="join|select|subselect"
|
|
batch-size="N"
|
|
access="field|property|ClassName"
|
|
optimistic-lock="true|false"
|
|
node="element-name|."
|
|
embed-xml="true|false"
|
|
>
|
|
|
|
<key .... />
|
|
<map-key .... />
|
|
<element .... />
|
|
</map>]]></programlisting>
|
|
<calloutlist>
|
|
<callout arearefs="mappingcollection1">
|
|
<para>
|
|
<literal>name</literal> el nombre de la propiedad de colección
|
|
</para>
|
|
</callout>
|
|
<callout arearefs="mappingcollection2">
|
|
<para>
|
|
<literal>table</literal> (opcional - por defecto al nombre de la propiedad)
|
|
el nombre de la tabla de coleciión (no usado para asociaciones
|
|
uno-a-muchos)
|
|
</para>
|
|
</callout>
|
|
<callout arearefs="mappingcollection3">
|
|
<para>
|
|
<literal>schema</literal> (opcional) el nombre de un esquema de tablas
|
|
para sobrescribir el esquema declarado en el elemento raíz
|
|
</para>
|
|
</callout>
|
|
<callout arearefs="mappingcollection4">
|
|
<para>
|
|
<literal>lazy</literal> (opcional - por defecto a <literal>true</literal>)
|
|
puede ser usado para deshabilitar la recuperación perezosa y
|
|
especificar que la asociación es siempre recuperada tempranamente
|
|
(no disponible para arrays)
|
|
</para>
|
|
</callout>
|
|
<callout arearefs="mappingcollection5">
|
|
<para>
|
|
<literal>inverse</literal> (opcional - por defecto a <literal>false</literal>)
|
|
marca esta colección como el extremo "inverso" de una asociación
|
|
bidireccional.
|
|
</para>
|
|
</callout>
|
|
<callout arearefs="mappingcollection6">
|
|
<para>
|
|
<literal>cascade</literal> (opcional - por defecto a <literal>none</literal>)
|
|
habilita operaciones en cascada a entidades hijas
|
|
</para>
|
|
</callout>
|
|
<callout arearefs="mappingcollection7">
|
|
<para>
|
|
<literal>sort</literal> (opcional) especifica una colección
|
|
con ordenamiento <literal>natural</literal>, o una clase comparadora
|
|
dada
|
|
</para>
|
|
</callout>
|
|
<callout arearefs="mappingcollection8">
|
|
<para>
|
|
<literal>order-by</literal> (opcional, sólo JDK1.4) especifica una columna
|
|
de tabla (o columnas) que definen el orden de iteración del
|
|
<literal>Map</literal>, <literal>Set</literal> o bag, junto a un
|
|
<literal>asc</literal> o <literal>desc</literal> opcional.
|
|
</para>
|
|
</callout>
|
|
<callout arearefs="mappingcollection9">
|
|
<para>
|
|
<literal>where</literal> (opcional) especifica una condición
|
|
<literal>WHERE</literal> de SQL arbitrario para ser usada al recuperar o
|
|
quitar la colección (útil si la colección debe
|
|
contener sólo un subconjunto de los datos disponibles)
|
|
</para>
|
|
</callout>
|
|
<callout arearefs="mappingcollection10">
|
|
<para>
|
|
<literal>fetch</literal> (opcional, por defecto a <literal>select</literal>)
|
|
Elige entre recuperación por unión externa (outer-join),
|
|
recuperar por selección secuencial, y recuperación por
|
|
subselección secuencial.
|
|
</para>
|
|
</callout>
|
|
<callout arearefs="mappingcollection11">
|
|
<para>
|
|
<literal>batch-size</literal> (opcional, por defecto a <literal>1</literal>)
|
|
especifica un "tamaño de lote" para la recuperar
|
|
perezosamente instancias de esta colección.
|
|
</para>
|
|
</callout>
|
|
<callout arearefs="mappingcollection12">
|
|
<para>
|
|
<literal>access</literal> (opcional - por defecto a <literal>property</literal>):
|
|
La estrategia que debe usar Hibernate para acceder al valor de la
|
|
propiedad.
|
|
</para>
|
|
</callout>
|
|
<callout arearefs="mappingcollection12">
|
|
<para>
|
|
<literal>optimistic-lock</literal> (opcional - por defecto a <literal>true</literal>):
|
|
Especifica que los cambios de estado de la colección resultan en
|
|
incrementos de versión de la entidad dueña. (Para asociaciones
|
|
uno a muchos, frecuentemente es razonable deshabilitar esta opción.)
|
|
</para>
|
|
</callout>
|
|
</calloutlist>
|
|
</programlistingco>
|
|
|
|
<sect2 id="collections-foreignkeys" >
|
|
<title>Claves foráneas de collección</title>
|
|
|
|
<para>
|
|
Las instancias de colección se distinguen en la base de datos por la
|
|
clave foránea de la entidad que posee la colección. Se hace
|
|
referencia a esta clave foránea como la <emphasis>columna clave de
|
|
colección</emphasis> (o columnas) de la tabla de colección.
|
|
La columna clave de la colección es mapeada por el elemento
|
|
<literal><key></literal>.
|
|
</para>
|
|
|
|
<para>
|
|
Puede haber una restricción de nulabilidad sobre la columna de clave
|
|
foránea. Para la mayoría de colecciones, esto está implicado.
|
|
Para asociaciones unidireccionales uno a muchos, la columna de clave
|
|
foránea es nulable por defecto, de modo que podrías necesitar
|
|
especificar <literal>not-null="true"</literal>.
|
|
</para>
|
|
|
|
<programlisting><![CDATA[<key column="productSerialNumber" not-null="true"/>]]></programlisting>
|
|
|
|
<para>
|
|
La restricción de clave foránea puede usar
|
|
<literal>ON DELETE CASCADE</literal>.
|
|
</para>
|
|
|
|
<programlisting><![CDATA[<key column="productSerialNumber" on-delete="cascade"/>]]></programlisting>
|
|
|
|
<para>
|
|
Mira el capítulo anterior por una definición completa del
|
|
elemento <literal><key></literal>.
|
|
</para>
|
|
|
|
</sect2>
|
|
|
|
<sect2 id="collections-elements" >
|
|
<title>Elementos de collección</title>
|
|
|
|
<para>
|
|
Las colecciones pueden contener casi cualquier tipo de Hibernate, incluyendo
|
|
todos los tipos básicos, componentes, y por supuesto, referencias a otras
|
|
entidades. Esta es una distinción importante: un objeto en una colección
|
|
puede ser manejado con una semántica de "valor" (su ciclo de vida depende
|
|
completamente del propietario de la colección) o podría ser una referencia
|
|
a otra entidad, con su propio ciclo de vida. En el último caso, sólo
|
|
el estado del "enlace" entre los dos objetos se considera mantenido por la
|
|
colección.
|
|
</para>
|
|
|
|
<para>
|
|
Se hace referencia al tipo contenido como el <emphasis>tipo de elemento
|
|
de la colección</emphasis>. Los elementos de colección son
|
|
mapeados por <literal><element></literal> o
|
|
<literal><composite-element></literal>, o en el caso de referencias
|
|
de entidades, con <literal><one-to-many></literal> o
|
|
<literal><many-to-many></literal>. Las dos primeras mapean elementos
|
|
con semántica de valor, los dos siguientes son usados para mapear
|
|
asociaciones de entidades.
|
|
</para>
|
|
|
|
</sect2>
|
|
|
|
<sect2 id="collections-indexed">
|
|
<title>Colecciones indexadas</title>
|
|
|
|
<para>
|
|
Todos los mapeos de colección, excepto aquellos con semántica de
|
|
set o bag, necesitan una <emphasis>columna índice</emphasis> en la tabla
|
|
de colección, una columna que mapea a un índice de array, o
|
|
índice de <literal>List</literal>, o clave de <literal>Map</literal>.
|
|
El índice de un <literal>Map</literal> puede ser de cualquier tipo
|
|
básico, mapeado con <literal><map-key></literal>, o puede ser
|
|
una referencia de entidad, mapeada con <literal><map-key-many-to-many></literal>,
|
|
o puede ser un tipo compuesto, mapeado con <literal><composite-map-key></literal>.
|
|
El índice de un array o lista es siempre de tipo <literal>integer</literal>
|
|
y se mapea usando el elemento <literal><list-index></literal>. La columna
|
|
mapeada contiene enteros secuenciales (numerados desde cero, por defecto).
|
|
</para>
|
|
|
|
<programlistingco>
|
|
<areaspec>
|
|
<area id="index1" coords="2 45"/>
|
|
<area id="index2" coords="3 45"/>
|
|
</areaspec>
|
|
<programlisting><![CDATA[<list-index
|
|
column="column_name"
|
|
base="0|1|..."/>]]></programlisting>
|
|
<calloutlist>
|
|
<callout arearefs="index1">
|
|
<para>
|
|
<literal>column_name</literal> (requerido): El nombre de la columna que tiene
|
|
los valores índice de la colección.
|
|
</para>
|
|
</callout>
|
|
<callout arearefs="index1">
|
|
<para>
|
|
<literal>base</literal> (opcional, por defecto a <literal>0</literal>): El valor
|
|
de la columna índice que corresponde al primer elemento de la lista o
|
|
array.
|
|
</para>
|
|
</callout>
|
|
</calloutlist>
|
|
</programlistingco>
|
|
|
|
<programlistingco>
|
|
<areaspec>
|
|
<area id="mapkey1" coords="2 45"/>
|
|
<area id="mapkey2" coords="3 45"/>
|
|
<area id="mapkey3" coords="4 45"/>
|
|
</areaspec>
|
|
<programlisting><![CDATA[<map-key
|
|
column="column_name"
|
|
formula="any SQL expression"
|
|
type="type_name"
|
|
node="@attribute-name"
|
|
length="N"/>]]></programlisting>
|
|
<calloutlist>
|
|
<callout arearefs="mapkey1">
|
|
<para>
|
|
<literal>column</literal> (opcional): El nombre de la columna que tiene
|
|
los valores índice de la colección.
|
|
</para>
|
|
</callout>
|
|
<callout arearefs="mapkey2">
|
|
<para>
|
|
<literal>formula</literal> (opcional): Una fórmula SQL usada para
|
|
evaluar la clave del mapa.
|
|
</para>
|
|
</callout>
|
|
<callout arearefs="mapkey3">
|
|
<para>
|
|
<literal>type</literal> (requerido): el tipo de las claves del mapa.
|
|
</para>
|
|
</callout>
|
|
</calloutlist>
|
|
</programlistingco>
|
|
|
|
<programlistingco>
|
|
<areaspec>
|
|
<area id="indexmanytomany1" coords="2 45"/>
|
|
<area id="indexmanytomany2" coords="3 45"/>
|
|
<area id="indexmanytomany3" coords="3 45"/>
|
|
</areaspec>
|
|
<programlisting><![CDATA[<map-key-many-to-many
|
|
column="column_name"
|
|
formula="any SQL expression"
|
|
class="ClassName"
|
|
/>]]></programlisting>
|
|
<calloutlist>
|
|
<callout arearefs="indexmanytomany1">
|
|
<para>
|
|
<literal>column</literal> (opcional): El nombre de la columna clave
|
|
foránea para los valores índice de la colección.
|
|
</para>
|
|
</callout>
|
|
<callout arearefs="indexmanytomany2">
|
|
<para>
|
|
<literal>formula</literal> (opcional): Una fórmula SQL usada para
|
|
evaluar la clave foránea de la clave del mapa.
|
|
</para>
|
|
</callout>
|
|
<callout arearefs="indexmanytomany3">
|
|
<para>
|
|
<literal>class</literal> (requerido): La clase de entidad usada como clave del mapa.
|
|
</para>
|
|
</callout>
|
|
</calloutlist>
|
|
</programlistingco>
|
|
|
|
|
|
<para>
|
|
Si tu tabla no tiene una columna índice, y deseas aún usar <literal>List</literal> como
|
|
tipo de propiedad, debes mapear la propiedad como un <emphasis><bag></emphasis>
|
|
de Hibernate. Un bag (bolsa) no retiene su orden al ser recuperado de la base de datos,
|
|
pero puede ser ordenado o clasificado opcionalmente.
|
|
</para>
|
|
|
|
</sect2>
|
|
|
|
<para>
|
|
Hay absolutamente un rango de mapeos que pueden ser generados para colecciones,
|
|
cubriendo muchos modelos relacionales comunes. Te sugerimos que experimentes con
|
|
la herramienta de generación de esquemas para obtener una idea de cómo varias
|
|
declaraciones de mapeo se traducen a tablas de base de datos.
|
|
</para>
|
|
|
|
<sect2 id="collections-ofvalues" revision="1">
|
|
<title>Colecciones de valores y asociaciones muchos-a-muchos</title>
|
|
|
|
<para>
|
|
Cualquier colección de valores o asociación muchos a muchos requiere una
|
|
<emphasis>tabla de colección</emphasis> dedicada con una columna o columnas
|
|
de clave foránea, <emphasis>columna de elemento de colección</emphasis> o
|
|
columnas y posiblemente una columna o columnas índice.
|
|
</para>
|
|
|
|
<para>
|
|
Para una colección de valores, usamos la etiqueta <literal><element></literal>.
|
|
</para>
|
|
|
|
<programlistingco>
|
|
<areaspec>
|
|
<area id="element1b" coords="2 50"/>
|
|
<area id="element2b" coords="3 50"/>
|
|
<area id="element3b" coords="4 50"/>
|
|
</areaspec>
|
|
<programlisting><![CDATA[<element
|
|
column="column_name"
|
|
formula="any SQL expression"
|
|
type="typename"
|
|
length="L"
|
|
precision="P"
|
|
scale="S"
|
|
not-null="true|false"
|
|
unique="true|false"
|
|
node="element-name"
|
|
/>]]></programlisting>
|
|
<calloutlist>
|
|
<callout arearefs="element1b">
|
|
<para>
|
|
<literal>column</literal> (opcional): El nombre de la columna que tiene
|
|
los valores de los elementos de la colección.
|
|
</para>
|
|
</callout>
|
|
<callout arearefs="element2b">
|
|
<para>
|
|
<literal>formula</literal> (opcional): Una fórmula SQL usada para evaluar
|
|
el elemento.
|
|
</para>
|
|
</callout>
|
|
<callout arearefs="element3b">
|
|
<para>
|
|
<literal>type</literal> (requerido): El tipo del elemento de colección.
|
|
</para>
|
|
</callout>
|
|
</calloutlist>
|
|
</programlistingco>
|
|
|
|
<para>
|
|
Una <emphasis>asociación muchos-a-muchos</emphasis> se especifica usando
|
|
el elemento <literal><many-to-many></literal>.
|
|
</para>
|
|
|
|
<programlistingco>
|
|
<areaspec>
|
|
<area id="manytomany1" coords="2 60"/>
|
|
<area id="manytomany2" coords="3 60"/>
|
|
<area id="manytomany3" coords="4 60"/>
|
|
<area id="manytomany4" coords="5 60"/>
|
|
<area id="manytomany5" coords="6 60"/>
|
|
<area id="manytomany6" coords="7 60"/>
|
|
<area id="manytomany7" coords="8 60"/>
|
|
</areaspec>
|
|
<programlisting><![CDATA[<many-to-many
|
|
column="column_name"
|
|
formula="any SQL expression"
|
|
class="ClassName"
|
|
fetch="select|join"
|
|
unique="true|false"
|
|
not-found="ignore|exception"
|
|
entity-name="EntityName"
|
|
node="element-name"
|
|
embed-xml="true|false"
|
|
/>]]></programlisting>
|
|
<calloutlist>
|
|
<callout arearefs="manytomany1">
|
|
<para>
|
|
<literal>column</literal> (opcional): El nombre de la columna de clave
|
|
foránea del elemento.
|
|
</para>
|
|
</callout>
|
|
<callout arearefs="manytomany2">
|
|
<para>
|
|
<literal>formula</literal> (opcional): Una fórmula SQL opcional usada
|
|
para evaluar el valor de clave foránea del elemento.
|
|
</para>
|
|
</callout>
|
|
<callout arearefs="manytomany3">
|
|
<para>
|
|
<literal>class</literal> (requerido): El nombre de la clase asociada.
|
|
</para>
|
|
</callout>
|
|
<callout arearefs="manytomany4">
|
|
<para>
|
|
<literal>fetch</literal> (opcional - por defecto a <literal>join</literal>):
|
|
habilita la recuperación por unión externa o selección secuencial para esta
|
|
asociación. Este es un caso especial; para una recuperación completamente
|
|
temprana (en un solo <literal>SELECT</literal>) de una entidad y sus relaciones
|
|
muchos-a-muchos a otras entidades, deberías habilitar la recuperación
|
|
<literal>join</literal> no sólo de la colección misma, sino también con este
|
|
atributo en el elemento anidado <literal><many-to-many></literal>.
|
|
</para>
|
|
</callout>
|
|
<callout arearefs="manytomany5">
|
|
<para>
|
|
<literal>unique</literal> (opcional): Habilita la generación DDL de una
|
|
restricción de unicidad para la columna clave foránea. Esto hace la
|
|
multiplicidad de la asociación efectivamente uno a muchos.
|
|
</para>
|
|
</callout>
|
|
<callout arearefs="manytomany6">
|
|
<para>
|
|
<literal>not-found</literal> (opcional - por defecto a <literal>exception</literal>):
|
|
Especifica cómo serán manejadas las claves foráneas que referencian
|
|
filas perdidas: <literal>ignore</literal> tratará una fila perdida como
|
|
una asociación nula.
|
|
</para>
|
|
</callout>
|
|
<callout arearefs="manytomany7">
|
|
<para>
|
|
<literal>entity-name</literal> (opcional): El nombre de entidad de la clase
|
|
asociada, como una alternativa a <literal>class</literal>.
|
|
</para>
|
|
</callout>
|
|
</calloutlist>
|
|
</programlistingco>
|
|
|
|
<para>
|
|
Algunos ejemplos, primero, un conjunto de cadenas:
|
|
</para>
|
|
|
|
<programlisting><![CDATA[<set name="names" table="person_names">
|
|
<key column="person_id"/>
|
|
<element column="person_name" type="string"/>
|
|
</set>]]></programlisting>
|
|
|
|
<para>
|
|
Un bag conteniendo enteros (con un orden de iteración determinado por el
|
|
atributo <literal>order-by</literal>):
|
|
</para>
|
|
|
|
<programlisting><![CDATA[<bag name="sizes"
|
|
table="item_sizes"
|
|
order-by="size asc">
|
|
<key column="item_id"/>
|
|
<element column="size" type="integer"/>
|
|
</bag>]]></programlisting>
|
|
|
|
<para>
|
|
Un array de entidades - en este caso, una asociación muchos a muchos:
|
|
</para>
|
|
|
|
<programlisting><![CDATA[<array name="addresses"
|
|
table="PersonAddress"
|
|
cascade="persist">
|
|
<key column="personId"/>
|
|
<list-index column="sortOrder"/>
|
|
<many-to-many column="addressId" class="Address"/>
|
|
</array>]]></programlisting>
|
|
|
|
<para>
|
|
Un mapa de índices de cadenas a fechas:
|
|
</para>
|
|
|
|
<programlisting><![CDATA[<map name="holidays"
|
|
table="holidays"
|
|
schema="dbo"
|
|
order-by="hol_name asc">
|
|
<key column="id"/>
|
|
<map-key column="hol_name" type="string"/>
|
|
<element column="hol_date" type="date"/>
|
|
</map>]]></programlisting>
|
|
|
|
<para>
|
|
Una lista de componentes (discutidos en el próximo capítulo):
|
|
</para>
|
|
|
|
<programlisting><![CDATA[<list name="carComponents"
|
|
table="CarComponents">
|
|
<key column="carId"/>
|
|
<list-index column="sortOrder"/>
|
|
<composite-element class="CarComponent">
|
|
<property name="price"/>
|
|
<property name="type"/>
|
|
<property name="serialNumber" column="serialNum"/>
|
|
</composite-element>
|
|
</list>]]></programlisting>
|
|
|
|
</sect2>
|
|
|
|
<sect2 id="collections-onetomany">
|
|
<title>Asociaciones uno-a-muchos</title>
|
|
|
|
<para>
|
|
Una <emphasis>asociación uno a muchos</emphasis> enlaza las tablas de dos clases
|
|
por medio de una clave foránea, sin intervención de tabla de colección alguna.
|
|
Este mapeo pierde cierta semántica de colecciones Java normales:
|
|
</para>
|
|
|
|
<itemizedlist spacing="compact">
|
|
<listitem>
|
|
<para>
|
|
Una instancia de la clase entidad contenida no puede pertenecer
|
|
a más de una instancia de la colección.
|
|
</para>
|
|
</listitem>
|
|
<listitem>
|
|
<para>
|
|
Una instancia de la clase entidad contenida no puede aparecer en más
|
|
de un valor del índice de colección.
|
|
</para>
|
|
</listitem>
|
|
</itemizedlist>
|
|
|
|
<para>
|
|
Una asociación de <literal>Product</literal> a <literal>Part</literal> requiere la
|
|
existencia de una columna clave foránea y posiblemente una columna índice a la tabla
|
|
<literal>Part</literal>. Una etiqueta <literal><one-to-many></literal> indica
|
|
que ésta es una asociación uno a muchos.
|
|
</para>
|
|
|
|
<programlistingco>
|
|
<areaspec>
|
|
<area id="onetomany1" coords="2 60"/>
|
|
<area id="onetomany2" coords="3 60"/>
|
|
<area id="onetomany3" coords="4 60"/>
|
|
</areaspec>
|
|
<programlisting><![CDATA[<one-to-many
|
|
class="ClassName"
|
|
not-found="ignore|exception"
|
|
entity-name="EntityName"
|
|
node="element-name"
|
|
embed-xml="true|false"
|
|
/>]]></programlisting>
|
|
<calloutlist>
|
|
<callout arearefs="onetomany1">
|
|
<para>
|
|
<literal>class</literal> (requerido): El nombre de la clase asociada.
|
|
</para>
|
|
</callout>
|
|
<callout arearefs="onetomany2">
|
|
<para>
|
|
<literal>not-found</literal> (opcional - por defecto a <literal>exception</literal>):
|
|
Especifica cómo serán manejados los identificadores en caché que referencien
|
|
filas perdidas: <literal>ignore</literal> tratará una fila perdida como una
|
|
asociación nula.
|
|
</para>
|
|
</callout>
|
|
<callout arearefs="onetomany3">
|
|
<para>
|
|
<literal>entity-name</literal> (opcional): El nombre de entidad de la clase
|
|
asociada, como una alternativa a <literal>class</literal>.
|
|
</para>
|
|
</callout>
|
|
</calloutlist>
|
|
</programlistingco>
|
|
|
|
<para>
|
|
Observa que el elemento <literal><one-to-many></literal> no necesita
|
|
declarar ninguna columna. Ni es necesario especificar el nombre de <literal>table</literal>
|
|
en ningún sitio.
|
|
</para>
|
|
|
|
<para>
|
|
<emphasis>Nota muy importante:</emphasis> Si la columna clave foránea de una asociación
|
|
<literal><one-to-many></literal> es declarada <literal>NOT NULL</literal>, debes
|
|
declarar el mapeo de <literal><key></literal> <literal>not-null="true"</literal>
|
|
o <emphasis>usar una asociación bidireccional</emphasis> con el mapeo de colección
|
|
marcado <literal>inverse="true"</literal>. Ver la discusión sobre asociaciones
|
|
bidireccionales más adelante en este capítulo.
|
|
</para>
|
|
|
|
<para>
|
|
Este ejemplo muestra un mapa de entidades <literal>Part</literal> por nombre
|
|
(donde <literal>partName</literal> es una propiedad persistente de <literal>Part</literal>).
|
|
Observa el uso de un índice basado en fórmula.
|
|
</para>
|
|
|
|
<programlisting><![CDATA[<map name="parts"
|
|
cascade="all">
|
|
<key column="productId" not-null="true"/>
|
|
<map-key formula="partName"/>
|
|
<one-to-many class="Part"/>
|
|
</map>]]></programlisting>
|
|
</sect2>
|
|
|
|
</sect1>
|
|
|
|
<sect1 id="collections-advancedmappings">
|
|
<title>Mapeos de colección avanzados</title>
|
|
|
|
<sect2 id="collections-sorted" revision="2">
|
|
<title>Colecciones ordenadas</title>
|
|
|
|
<para>
|
|
Hibernate soporta colecciones implementando <literal>java.util.SortedMap</literal> y
|
|
<literal>java.util.SortedSet</literal>. Debes especificar un comparador en el fichero de
|
|
mapeo:
|
|
</para>
|
|
|
|
<programlisting><![CDATA[<set name="aliases"
|
|
table="person_aliases"
|
|
sort="natural">
|
|
<key column="person"/>
|
|
<element column="name" type="string"/>
|
|
</set>
|
|
|
|
<map name="holidays" sort="my.custom.HolidayComparator">
|
|
<key column="year_id"/>
|
|
<map-key column="hol_name" type="string"/>
|
|
<element column="hol_date" type="date"/>
|
|
</map>]]></programlisting>
|
|
|
|
<para>
|
|
Los valores permitidos del atributo <literal>sort</literal> son <literal>unsorted</literal>,
|
|
<literal>natural</literal> y el nombre de una clase que implemente
|
|
<literal>java.util.Comparator</literal>.
|
|
</para>
|
|
|
|
<para>
|
|
Las colecciones ordenadas realmente se comportan como <literal>java.util.TreeSet</literal> o
|
|
<literal>java.util.TreeMap</literal>.
|
|
</para>
|
|
|
|
<para>
|
|
Si quieres que la misma base de datos ordene los elementos de colección usa el
|
|
atributo <literal>order-by</literal> de los mapeos <literal>set</literal>,
|
|
<literal>bag</literal> o <literal>map</literal>. Esta solución está disponible sólo
|
|
bajo el JDK 1.4 o superior (está implementado usando <literal>LinkedHashSet</literal> o
|
|
<literal>LinkedHashMap</literal>). Esto realiza la ordenación en la consulta SQL,
|
|
no en memoria.
|
|
</para>
|
|
|
|
<programlisting><![CDATA[<set name="aliases" table="person_aliases" order-by="lower(name) asc">
|
|
<key column="person"/>
|
|
<element column="name" type="string"/>
|
|
</set>
|
|
|
|
<map name="holidays" order-by="hol_date, hol_name">
|
|
<key column="year_id"/>
|
|
<map-key column="hol_name" type="string"/>
|
|
<element column="hol_date type="date"/>
|
|
</map>]]></programlisting>
|
|
|
|
<para>
|
|
Observa que el valor del atributo <literal>order-by</literal> es una ordenación SQL, no
|
|
una ordenación HQL!
|
|
</para>
|
|
|
|
<para>
|
|
Las asociaciones pueden incluso ser ordenadas por algún criterio arbitrario en tiempo de
|
|
ejecución usando un <literal>filter()</literal> de colección.
|
|
</para>
|
|
|
|
<programlisting><![CDATA[sortedUsers = s.createFilter( group.getUsers(), "order by this.name" ).list();]]></programlisting>
|
|
|
|
</sect2>
|
|
|
|
<sect2 id="collections-bidirectional" revision="1">
|
|
<title>Asociaciones bidireccionales</title>
|
|
|
|
<para>
|
|
Una <emphasis>asociación bidireccional</emphasis> permite la nevegación desde
|
|
ambos "extremos" de la asociación. Son soportados dos tipos de asociación
|
|
bidireccional:
|
|
|
|
<variablelist>
|
|
<varlistentry>
|
|
<term>uno-a-muchos</term>
|
|
<listitem>
|
|
<para>
|
|
set o bag valorados en un extremo, monovaluados al otro
|
|
</para>
|
|
</listitem>
|
|
</varlistentry>
|
|
<varlistentry>
|
|
<term>muchos-a-muchos</term>
|
|
<listitem>
|
|
<para>
|
|
set o bag valorados a ambos extremos
|
|
</para>
|
|
</listitem>
|
|
</varlistentry>
|
|
</variablelist>
|
|
|
|
</para>
|
|
|
|
<para>
|
|
Puedes especificar una asociación bidireccional muchos-a-muchos simplemente
|
|
mapeando dos asociaciones muchos-a-muchos a la misma tabla de base de datos
|
|
y declarando un extremo como <emphasis>inverse</emphasis> (cuál de ellos es tu
|
|
elección, pero no puede ser una colección indexada).
|
|
</para>
|
|
|
|
<para>
|
|
He aquí un ejemplo de una asociación bidireccional muchos-a-muchos; cada categoría
|
|
puede tener muchos ítems y cada ítem puede estar en muchas categorías:
|
|
</para>
|
|
|
|
<programlisting><![CDATA[<class name="Category">
|
|
<id name="id" column="CATEGORY_ID"/>
|
|
...
|
|
<bag name="items" table="CATEGORY_ITEM">
|
|
<key column="CATEGORY_ID"/>
|
|
<many-to-many class="Item" column="ITEM_ID"/>
|
|
</bag>
|
|
</class>
|
|
|
|
<class name="Item">
|
|
<id name="id" column="CATEGORY_ID"/>
|
|
...
|
|
|
|
<!-- inverse end -->
|
|
<bag name="categories" table="CATEGORY_ITEM" inverse="true">
|
|
<key column="ITEM_ID"/>
|
|
<many-to-many class="Category" column="CATEGORY_ID"/>
|
|
</bag>
|
|
</class>]]></programlisting>
|
|
|
|
<para>
|
|
Los cambios hechos sólo al extremo inverso de la asociación <emphasis>no</emphasis>
|
|
son persistidos. Esto significa que Hibernate tiene dos representaciones en memoria
|
|
para cada asociación bidireccional, una enlaza de A a B y otra enlaza de B a A.
|
|
Esto es más fácil de entender si piensas en el modelo de objetos de Java y cómo
|
|
creamos una relación muchos-a-muchos en Java:
|
|
</para>
|
|
|
|
<programlisting><![CDATA[
|
|
category.getItems().add(item); // The category now "knows" about the relationship
|
|
item.getCategories().add(category); // The item now "knows" about the relationship
|
|
|
|
session.persist(item); // The relationship won't be saved!
|
|
session.persist(category); // The relationship will be saved]]></programlisting>
|
|
|
|
<para>
|
|
El lado no-inverso se usa para salvar la representación en memoria a la base de datos.
|
|
</para>
|
|
|
|
<para>
|
|
Puedes definir una asociación bidireccional uno-a-muchos mapeando una asociación uno-a-muchos
|
|
a la misma columna (o columnas) de tabla como una asociación muchos-a-uno y declarando el
|
|
extremo multivaluado <literal>inverse="true"</literal>.
|
|
</para>
|
|
|
|
<programlisting><![CDATA[<class name="Parent">
|
|
<id name="id" column="parent_id"/>
|
|
....
|
|
<set name="children" inverse="true">
|
|
<key column="parent_id"/>
|
|
<one-to-many class="Child"/>
|
|
</set>
|
|
</class>
|
|
|
|
<class name="eg.Child">
|
|
<id name="id" column="id"/>
|
|
....
|
|
<many-to-one name="parent"
|
|
class="Parent"
|
|
column="parent_id"
|
|
not-null="true"/>
|
|
</class>]]></programlisting>
|
|
|
|
<para>
|
|
Mapear un extremo de una asociación con <literal>inverse="true"</literal> no afecta
|
|
la operación de cascadas; éstos son conceptos ortogonales!
|
|
</para>
|
|
|
|
</sect2>
|
|
|
|
<sect2 id="collections-ternary">
|
|
<title>Asociaciones ternarias</title>
|
|
|
|
<para>
|
|
Hay tres enfoques posibles para mapear una asociación ternaria.
|
|
Una es usar un <literal>Map</literal> con una asociación como su índice:
|
|
</para>
|
|
|
|
<programlisting><![CDATA[<map name="contracts">
|
|
<key column="employer_id" not-null="true"/>
|
|
<map-key-many-to-many column="employee_id" class="Employee"/>
|
|
<one-to-many class="Contract"/>
|
|
</map>]]></programlisting>
|
|
|
|
<programlisting><![CDATA[<map name="connections">
|
|
<key column="incoming_node_id"/>
|
|
<map-key-many-to-many column="outgoing_node_id" class="Node"/>
|
|
<many-to-many column="connection_id" class="Connection"/>
|
|
</map>]]></programlisting>
|
|
|
|
<para>
|
|
Un segundo enfoque es simplemente remodelar la asociación como una clase de entidad.
|
|
Este es el enfoque que usamos más comunmente.
|
|
</para>
|
|
|
|
<para>
|
|
Una alternativa final es usar elementos compuestos, que discutiremos más adelante.
|
|
</para>
|
|
|
|
</sect2>
|
|
|
|
<sect2 id="collections-idbag" revision="1">
|
|
<title><literal>Usando un <idbag></literal></title>
|
|
|
|
<para>
|
|
Si has adoptado completamente nuestra visión de que las claves compuestas son una
|
|
cosa mala y que las entidades deben tener identificadores sitéticos (claves delegadas),
|
|
entonces podrías encontrar un poco raro que todas las asociaciones muchos a muchos y
|
|
las colecciones de valores que hemos mostrado hasta ahora mapeen a tablas con claves
|
|
compuestas! Ahora, este punto es discutible; una tabla de pura asociación no parece
|
|
beneficiarse demasiado de una clave delegada (aunque sí <emphasis>podría</emphasis> una
|
|
colección de valores compuestos). Sin embargo, Hibernate provee una funcionalidad que
|
|
te permite mapear asociaciones muchos a muchos y colecciones de valores a una tabla con
|
|
una clave delegada.
|
|
</para>
|
|
|
|
<para>
|
|
El elemento <literal><idbag></literal> te permite mapear una <literal>List</literal>
|
|
(o <literal>Collection</literal>) con semántica de bag.
|
|
</para>
|
|
|
|
<programlisting><![CDATA[<idbag name="lovers" table="LOVERS">
|
|
<collection-id column="ID" type="long">
|
|
<generator class="sequence"/>
|
|
</collection-id>
|
|
<key column="PERSON1"/>
|
|
<many-to-many column="PERSON2" class="eg.Person" outer-join="true"/>
|
|
</idbag>]]></programlisting>
|
|
|
|
<para>
|
|
Como puedes ver, un <literal><idbag></literal> tiene un generador de id sintético,
|
|
igual que una clase de entidad! Una clave delegada diferente se asigna a cada fila
|
|
de la colección. Hibernate no provee ningún mecanismo para descubrir el valor de clave
|
|
delegada de una fila en particular, sin embargo.
|
|
</para>
|
|
|
|
<para>
|
|
Observa que el rendimiento de actualización de un <literal><idbag></literal> es
|
|
<emphasis>mucho</emphasis> mejor que el de un <literal><bag></literal> regular!
|
|
Hibernate puede localizar filas individuales eficientemente y actualizarlas o borrarlas
|
|
individualmente, igual que si fuese una lista, mapa o conjunto.
|
|
</para>
|
|
|
|
<para>
|
|
En la implementación actual, la estrategia de generación de identificador
|
|
<literal>native</literal> no está soportada para identificadores de colecciones
|
|
<literal><idbag></literal>.
|
|
</para>
|
|
|
|
</sect2>
|
|
|
|
</sect1>
|
|
|
|
<!--undocumenting this stuff -->
|
|
|
|
<!--sect1 id="collections-heterogeneous">
|
|
<title>Heterogeneous Associations</title>
|
|
|
|
<para>
|
|
The <literal><many-to-any></literal> and <literal><index-many-to-any></literal>
|
|
elements provide for true heterogeneous associations. These mapping elements work in the
|
|
same way as the <literal><any></literal> element - and should also be used
|
|
rarely, if ever.
|
|
</para>
|
|
|
|
</sect1-->
|
|
|
|
<sect1 id="collections-example" revision="1">
|
|
<title>Ejemplos de colección</title>
|
|
|
|
<para>
|
|
Las secciones previas son bastantes confusas. Así que miremos un ejemplo.
|
|
Esta clase:
|
|
</para>
|
|
|
|
<programlisting><![CDATA[package eg;
|
|
import java.util.Set;
|
|
|
|
public class Parent {
|
|
private long id;
|
|
private Set children;
|
|
|
|
public long getId() { return id; }
|
|
private void setId(long id) { this.id=id; }
|
|
|
|
private Set getChildren() { return children; }
|
|
private void setChildren(Set children) { this.children=children; }
|
|
|
|
....
|
|
....
|
|
}]]></programlisting>
|
|
|
|
<para>
|
|
tiene una colección de instancias de <literal>Child</literal>.
|
|
Si cada hijo tiene como mucho un padre, el mapeo más natural es
|
|
una asociación uno-a-muchos:
|
|
</para>
|
|
|
|
<programlisting><![CDATA[<hibernate-mapping>
|
|
|
|
<class name="Parent">
|
|
<id name="id">
|
|
<generator class="sequence"/>
|
|
</id>
|
|
<set name="children">
|
|
<key column="parent_id"/>
|
|
<one-to-many class="Child"/>
|
|
</set>
|
|
</class>
|
|
|
|
<class name="Child">
|
|
<id name="id">
|
|
<generator class="sequence"/>
|
|
</id>
|
|
<property name="name"/>
|
|
</class>
|
|
|
|
</hibernate-mapping>]]></programlisting>
|
|
|
|
<para>
|
|
Esto mapea a las siguientes definiciones de tablas:
|
|
</para>
|
|
|
|
<programlisting><![CDATA[create table parent ( id bigint not null primary key )
|
|
create table child ( id bigint not null primary key, name varchar(255), parent_id bigint )
|
|
alter table child add constraint childfk0 (parent_id) references parent]]></programlisting>
|
|
|
|
<para>
|
|
Si el padre es <emphasis>requerido</emphasis>, usa una asociación bidireccional
|
|
uno-a-muchos:
|
|
</para>
|
|
|
|
<programlisting><![CDATA[<hibernate-mapping>
|
|
|
|
<class name="Parent">
|
|
<id name="id">
|
|
<generator class="sequence"/>
|
|
</id>
|
|
<set name="children" inverse="true">
|
|
<key column="parent_id"/>
|
|
<one-to-many class="Child"/>
|
|
</set>
|
|
</class>
|
|
|
|
<class name="Child">
|
|
<id name="id">
|
|
<generator class="sequence"/>
|
|
</id>
|
|
<property name="name"/>
|
|
<many-to-one name="parent" class="Parent" column="parent_id" not-null="true"/>
|
|
</class>
|
|
|
|
</hibernate-mapping>]]></programlisting>
|
|
|
|
<para>
|
|
Observa la restricción <literal>NOT NULL</literal>:
|
|
</para>
|
|
|
|
<programlisting><![CDATA[create table parent ( id bigint not null primary key )
|
|
create table child ( id bigint not null
|
|
primary key,
|
|
name varchar(255),
|
|
parent_id bigint not null )
|
|
alter table child add constraint childfk0 (parent_id) references parent]]></programlisting>
|
|
|
|
<para>
|
|
Alternativamente, si absolutamente insistes que esta asociación debe ser unidireccional,
|
|
puedes declarar la restricción <literal>NOT NULL</literal> en el mapeo de
|
|
<literal><key></literal>:
|
|
</para>
|
|
|
|
<programlisting><![CDATA[<hibernate-mapping>
|
|
|
|
<class name="Parent">
|
|
<id name="id">
|
|
<generator class="sequence"/>
|
|
</id>
|
|
<set name="children">
|
|
<key column="parent_id" not-null="true"/>
|
|
<one-to-many class="Child"/>
|
|
</set>
|
|
</class>
|
|
|
|
<class name="Child">
|
|
<id name="id">
|
|
<generator class="sequence"/>
|
|
</id>
|
|
<property name="name"/>
|
|
</class>
|
|
|
|
</hibernate-mapping>]]></programlisting>
|
|
|
|
<para>
|
|
En la otra mano, si un hijo pudiera tener múltiples padres, sería apropiada
|
|
una asociación muchos-a-muchos:
|
|
</para>
|
|
|
|
<programlisting><![CDATA[<hibernate-mapping>
|
|
|
|
<class name="Parent">
|
|
<id name="id">
|
|
<generator class="sequence"/>
|
|
</id>
|
|
<set name="children" table="childset">
|
|
<key column="parent_id"/>
|
|
<many-to-many class="Child" column="child_id"/>
|
|
</set>
|
|
</class>
|
|
|
|
<class name="Child">
|
|
<id name="id">
|
|
<generator class="sequence"/>
|
|
</id>
|
|
<property name="name"/>
|
|
</class>
|
|
|
|
</hibernate-mapping>]]></programlisting>
|
|
|
|
<para>
|
|
Definiciones de tabla:
|
|
</para>
|
|
|
|
<programlisting><![CDATA[create table parent ( id bigint not null primary key )
|
|
create table child ( id bigint not null primary key, name varchar(255) )
|
|
create table childset ( parent_id bigint not null,
|
|
child_id bigint not null,
|
|
primary key ( parent_id, child_id ) )
|
|
alter table childset add constraint childsetfk0 (parent_id) references parent
|
|
alter table childset add constraint childsetfk1 (child_id) references child]]></programlisting>
|
|
|
|
<para>
|
|
Para más ejemplos y un paseo completo a través del mapeo de relaciones padre/hijo,
|
|
ver <xref linkend="example-parentchild"/>.
|
|
</para>
|
|
|
|
<para>
|
|
Son posibles mapeos de asociación aún más complejos. Catalogaremos todas las posibilidades
|
|
en el próximo capítulo.
|
|
</para>
|
|
|
|
</sect1>
|
|
|
|
</chapter>
|