Hibernate.orgCommunity Documentation

Capítulo 6. Mapeos de colección

6.1. Colecciones persistentes
6.2. Mapeos de colección
6.2.1. Claves foráneas de colección
6.2.2. Elementos de collección
6.2.3. Colecciones indexadas
6.2.4. Colecciones de valores y asociaciones muchos-a-muchos
6.2.5. Asociaciones uno-a-muchos
6.3. Mapeos de colección avanzados
6.3.1. Colecciones ordenadas
6.3.2. Asociaciones bidireccionales
6.3.3. Asociaciones bidireccionales con colecciones indexadas
6.3.4. Asociaciones ternarias
6.3.5. Using an <idbag>
6.4. Ejemplos de colección

Hibernate requiere que los campos valuados en colección persistente se declaren como un tipo de interfaz. Por ejemplo:

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; }
}

La interfaz real puede ser java.util.Set, java.util.Collection, java.util.List, java.util.Map, java.util.SortedSet, java.util.SortedMap o lo que usted quiera (donde "lo que usted quiera" significa que tendrá que escribir una implementación de org.hibernate.usertype.UserCollectionType).

Note cómo se inicializó la variable de instancia con una instancia de HashSet. Esta es la mejor forma de inicializar las propiedades valuadas en colección de instancias recién instanciadas (no persistentes). Cuando hace persistente la instancia, llamando a persist(), por ejemplo, Hibernate realmente remplazará el HashSet con una instancia de una implementación de Set propia de Hibernate. Observe los siguientes errores:

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!

Las colecciones persistentes inyectadas por Hibernate se comportan como HashMap, HashSet, TreeMap, TreeSet o ArrayList, dependiendo del tipo de interfaz.

Las instancias de colecciones tienen el comportamiento usual de los tipos de valor. Son automáticamente persistidas al ser referenciadas por un objeto persistente y se borran automáticamente al desreferenciarse. Si una colección se pasa de un objeto persistente a otro, puede que sus elementos se muevan de una tabla a otra. Dos entidades no pueden 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.

Utilice las colecciones persistentes de la misma forma en que utiliza colecciones de Java ordinarias. Sin embargo, asegúrese de que entiende la semántica de las asociaciones bidireccionales (se discuten más adelante).

El elemento de mapeo de Hibernate usado para mapear una colección depende del tipo de la interfaz. Por ejemplo, un elemento <set> se utiliza para mapear propiedades de tipo Set.

<class name="Product">

    <id name="serialNumber" column="productSerialNumber"/>
    <set name="parts">
        <key column="productSerialNumber" not-null="true"/>
        <one-to-many class="Part"/>
    </set>
</class
>

Aparte de <set>, existen además los elementos de mapeo <list>, <map>, <bag>, <array> y <primitive-array>. El elemento <map> es representativo:

<map
    name="prop(1)ertyName"
    table="tab(2)le_name"
    schema="sc(3)hema_name"
    lazy="true(4)|extra|false"
    inverse="t(5)rue|false"
    cascade="a(6)ll|none|save-update|delete|all-delete-orphan|delete-orphan"
    sort="unso(7)rted|natural|comparatorClass"
    order-by="(8)column_name asc|desc"
    where="arb(9)itrary sql where condition"
    fetch="joi(10)n|select|subselect"
    batch-size(11)="N"
    access="fi(12)eld|property|ClassName"
    optimistic(13)-lock="true|false"
    mutable="t(14)rue|false"
    node="element-name|."
    embed-xml="true|false"
>

    <key .... />
    <map-key .... />
    <element .... />
</map
>

1

name: el nombre de la propiedad de colección

2

table (opcional - por defecto es el nombre de la propiedad): el nombre de la tabla de colección. No se utiliza para asociaciones uno-a-muchos.

3

schema (opcional): el nombre de un esquema de tablas para sobrescribir el esquema declarado en el elemento raíz

4

lazy (opcional - por defecto es true): deshabilita la recuperación perezosa y especifica que la asociación siempre es recuperada tempranamente. También se puede utilizar para activar una recuperación "extra-perezoza", en donde la mayoría de las operaciones no inicializan la colección. Esto es apropiado para colecciones grandes.

5

inverse (opcional - por defecto es false): marca esta colección como el extremo "inverso" de una asociación bidireccional.

6

cascade (opcional - por defecto es none): habilita operaciones en cascada para entidades hijas.

7

sort (opcional): especifica una colección con ordenamiento natural, o una clase comparadora dada.

8

order-by (opcional, sólamente en JDK1.4): especifica una columna de tabla o columnas que definen el orden de iteración del Map, Set o bag junto con un asc o desc opcional.

9

where (opcional): especifica una condición WHERE de SQL arbitraria que se utiliza al recuperar o quitar la colección. Esto es útil si la colección debe contener sólamente un subconjunto de los datos disponibles.

10

fetch (opcional, por defecto es select): Elige entre la recuperación por unión externa (outer-join), la recuperación por selección secuencial y la recuperación por subselección secuencial.

11

batch-size (opcional, por defecto es 1): especifica un "tamaño de lote" para recuperar perezosamente instancias de esta colección.

12

access (opcional - por defecto es property): La estrategia que Hibernate utiliza para acceder al valor de la propiedad de colección.

13

optimistic-lock (opcional - por defecto es true): Especifica que los cambios de estado de la colección causan incrementos de la versión de la entidad dueña. Para asociaciones uno a muchos, es posible que quiera deshabilitar esta opción.

14

mutable (opcional - por defectos es true): Un valor false especifica que los elementos de la colección nunca cambian. En algunos casos, esto permite una pequeña optimización de rendimiento.

Todos los mapeos de colección, excepto aquellos con semántica de set o bag, necesitan una columna índice en la tabla de colección. Una columna índice es una columna que mapea a un índice de array o índice de List o llave de Map. El índice de un Map puede ser de cualquier tipo básico, mapeado con <map-key>. Puede ser una referencia de entidad mapeada con <map-key-many-to-many>, o puede ser un tipo compuesto mapeado con <composite-map-key>. El índice de un array o lista es siempre de tipo integer y se mapea utilizando el elemento <list-index>. La columna mapeada contiene enteros secuenciales numerados desde cero, por defecto.

<list-index
        column(1)="column_name"
        base="(2)0|1|..."/>

1

column_name (required): the name of the column holding the collection index values.

1

base (optional - defaults to 0): the value of the index column that corresponds to the first element of the list or array.

<map-key
        column(1)="column_name"
        formul(2)a="any SQL expression"
        type="(3)type_name"
        node="@attribute-name"
        length="N"/>

1

column (optional): the name of the column holding the collection index values.

2

formula (optional): a SQL formula used to evaluate the key of the map.

3

type (required): the type of the map keys.

<map-key-many-to-many
        column(1)="column_name"
        formul(2)(3)a="any SQL expression"
        class="ClassName"
/>

1

column (optional): the name of the foreign key column for the collection index values.

2

formula (optional): a SQ formula used to evaluate the foreign key of the map key.

3

class (required): the entity class used as the map key.

Si su tabla no tiene una columna índice y todavía desea utilizar List como tipo de propiedad, puede mapear la propiedad como un <bag> de Hibernate. Un bag (bolsa) no retiene su orden al ser recuperado de la base de datos, pero puede ser ordenado o clasificado de manera opcional.

Cualquier colección de valores o asociación muchos-a-muchos requiere una tabla de colección dedicada con una columna o columnas de clave foránea, columna de elemento de colección o columnas y posiblemente una columna o columnas índices.

Para una colección de valores utilice la etiqueta <element>. Por ejemplo:

<element
        column(1)="column_name"
        formul(2)a="any SQL expression"
        type="(3)typename"
        length="L"
        precision="P"
        scale="S"
        not-null="true|false"
        unique="true|false"
        node="element-name"
/>

1

column (optional): the name of the column holding the collection element values.

2

formula (optional): an SQL formula used to evaluate the element.

3

type (required): the type of the collection element.

A many-to-many association is specified using the <many-to-many> element.

<many-to-many
        column(1)="column_name"
        formul(2)a="any SQL expression"
        class=(3)"ClassName"
        fetch=(4)"select|join"
        unique(5)="true|false"
        not-fo(6)und="ignore|exception"
        entity(7)-name="EntityName"
        proper(8)ty-ref="propertyNameFromAssociatedClass"
        node="element-name"
        embed-xml="true|false"
    />

1

column (optional): the name of the element foreign key column.

2

formula (optional): an SQL formula used to evaluate the element foreign key value.

3

class (required): the name of the associated class.

4

fetch (optional - defaults to join): enables outer-join or sequential select fetching for this association. This is a special case; for full eager fetching in a single SELECT of an entity and its many-to-many relationships to other entities, you would enable join fetching,not only of the collection itself, but also with this attribute on the <many-to-many> nested element.

5

unique (optional): enables the DDL generation of a unique constraint for the foreign-key column. This makes the association multiplicity effectively one-to-many.

6

not-found (optional - defaults to exception): specifies how foreign keys that reference missing rows will be handled: ignore will treat a missing row as a null association.

7

entity-name (optional): the entity name of the associated class, as an alternative to class.

8

property-ref (optional): the name of a property of the associated class that is joined to this foreign key. If not specified, the primary key of the associated class is used.

Aquí tiene algunos ejemplos:

Un grupo de cadenas:


<set name="names" table="person_names">
    <key column="person_id"/>
    <element column="person_name" type="string"/>
</set
>

Un bag que contiene enteros con un orden de iteración determinado por el atributo order-by:


<bag name="sizes"
        table="item_sizes" 
        order-by="size asc">
    <key column="item_id"/>
    <element column="size" type="integer"/>
</bag
>

Una lista de entidades, en este caso, una asociación muchos-a-muchos:


<array name="addresses"
        table="PersonAddress" 
        cascade="persist">
    <key column="personId"/>
    <list-index column="sortOrder"/>
    <many-to-many column="addressId" class="Address"/>
</array
>

Un mapeo de índices de cadenas a fechas:


<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
>

Una lista de componentes (se discuten en el siguiente capítulo):


<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
>

Una asociación uno-a-muchos 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:

Una asociación de Product a Part requiere la existencia de una columna clave foránea y posiblemente una columna índice a la tabla Part. Una etiqueta <one-to-many> indica que ésta es una asociación uno-a-muchos.

<one-to-many
        class=(1)"ClassName"
        not-fo(2)und="ignore|exception"
        entity(3)-name="EntityName"
        node="element-name"
        embed-xml="true|false"
    />

1

class (requerido): El nombre de la clase asociada.

2

not-found (opcional - por defecto es exception): Especifica cómo serán manejados los identificadores en caché que referencien filas perdidas. ignore tratará una fila perdida como una asociación nula.

3

entity-name (opcional): El nombre de entidad de la clase asociada como una alternativa para class.

El elemento <one-to-many> no necesita declarar ninguna columna. Ni es necesario especificar el nombre de table en ningún sitio.

El siguiente ejemplo muestra un mapeo de entidades Part por nombre, en donde partName es una propiedad persistente de Part. Observe el uso de un índice basado en fórmula:


<map name="parts"
        cascade="all">
    <key column="productId" not-null="true"/>
    <map-key formula="partName"/>
    <one-to-many class="Part"/>
</map
>

Hibernate soporta colecciones implementando java.util.SortedMap y java.util.SortedSet. Tiene que especificar un comparador en el archivo de mapeo:


<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
>

Los valores permitidos del atributo sort son unsorted, natural y el nombre de una clase que implemente java.util.Comparator.

Las colecciones ordenadas realmente se comportan como java.util.TreeSet o java.util.TreeMap.

Si quiere que la misma base de datos ordene los elementos de la colección, utilice el atributo order-by de los mapeos set, bag o map. Esta solución está disponible sólamente bajo el JDK 1.4 o superior y se implementa utilizando LinkedHashSet o LinkedHashMap. Este realiza la ordenación en la consulta SQL y no en memoria.


<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
>

Las asociaciones pueden incluso ser ordenadas por algún criterio arbitrario en tiempo de ejecución utilizando un filter() de colección:

sortedUsers = s.createFilter( group.getUsers(), "order by this.name" ).list();

Una asociación bidireccional permite la navegación desde ambos "extremos" de la asociación. Se soportan dos tipos de asociación bidireccional:

Puede 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 inverso (cuál de ellos, usted escoge, pero no puede ser una colección indexada).

He aquí un ejemplo de una asociación bidireccional muchos-a-muchos que ilustra la manera en que cada categoría puede tener muchos ítems y cada ítem puede estar en muchas categorías:


<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="ITEM_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
>

Los cambios realizados sólamente al extremo inverso de la asociación no son persistidos. Esto significa que Hibernate tiene dos representaciones en memoria para cada asociación bidireccional: un enlace de A a B y otro enlace de B a A. Esto es más fácil de entender si piensa en el modelo de objetos de Java y cómo creamos una relación muchos-a-muchos en Java:



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

El lado no-inverso se utiliza para guardar la representación en memoria a la base de datos.

Puede 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 inverse="true".


<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="Child">
    <id name="id" column="child_id"/>
    ....
    <many-to-one name="parent" 
        class="Parent" 
        column="parent_id"
        not-null="true"/>
</class
>

Mapear un extremo de una asociación con inverse="true" no afecta la operación de cascadas ay que éstos son conceptos ortogonales.

Requiere especial atención una asociación bidireccional en donde un extremo esté representado como una <list> o <map>. Si hay una propiedad de la clase hija que mapee a la columna índice, puede utilizar inverse="true" en el mapeo de la colección:


<class name="Parent">
    <id name="id" column="parent_id"/>
    ....
    <map name="children" inverse="true">
        <key column="parent_id"/>
        <map-key column="name" 
            type="string"/>
        <one-to-many class="Child"/>
    </map>
</class>

<class name="Child">
    <id name="id" column="child_id"/>
    ....
    <property name="name" 
        not-null="true"/>
    <many-to-one name="parent" 
        class="Parent" 
        column="parent_id"
        not-null="true"/>
</class
>

Si no existe tal propiedad en la clase hija, no podemos considerar la asociación como verdaderamente bidireccional. Es decir, hay información en un extremo de la asociación que no está disponible en el otro extremo. En este caso, no puede mapear la colección con inverse="true". En cambio, puede usar el siguiente mapeo:


<class name="Parent">
    <id name="id" column="parent_id"/>
    ....
    <map name="children">
        <key column="parent_id"
            not-null="true"/>
        <map-key column="name" 
            type="string"/>
        <one-to-many class="Child"/>
    </map>
</class>

<class name="Child">
    <id name="id" column="child_id"/>
    ....
    <many-to-one name="parent" 
        class="Parent" 
        column="parent_id"
        insert="false"
        update="false"
        not-null="true"/>
</class
>

Note que en este mapeo, el extremo de la asociación valuado en colección es responsable de las actualizaciones de la clave foránea.

La mayoría de las asociaciones muchos-a-muchos y las colecciones de valores que hemos mostrado hasta ahora mapean a tablas con claves compuestas, aunque se ha sugerido que las entidades deben tener identificadores sintéticos (llaves sustitutas). Una tabla de pura asociación no parece beneficiarse mucho de una llave sustituta, aunque sí podría beneficiarse una colección de valores compuestos. Es por esto que Hibernate brinda una funcionalidad que le permite mapear asociaciones muchos a muchos y colecciones de valores a una tabla con una llave sustituta.

El elemento <idbag> le permite mapear una List (o Collection) con semántica de bag. Por ejemplo:


<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="Person" fetch="join"/>
</idbag
>

Un <idbag> tiene un generador de id sintético, al igual que una clase de entidad. Se asigna una clave delegada diferente a cada fila de la colección. Sin embargo, Hibernate no proporciona ningún mecanismo para descubrir el valor de la clave delegada de una fila en particular.

El rendimiento de actualización de un <idbag> es mucho mejor que el de un <bag> normal. Hibernate puede localizar filas individuales eficientemente y actualizarlas o borrarlas individualmente, al igual que si fuese una lista, mapa o conjunto.

En la implementación actual, la estrategia de generación de identificador native no se encuentra soportada para identificadores de colecciones <idbag>.

Esta sección cubre los ejemplos de colección.

La siguiente clase tiene una colección de instancias Child:

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; }
    ....
    ....
}

Si cada hijo tiene como mucho un padre, el mapeo más natural es una asociación uno-a-muchos:


<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
>

Esto mapea a las siguientes definiciones de tabla:


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

Si el padre es requerido, utilice una asociación bidireccional uno-a-muchos:


<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
>

Observe la restricción NOT NULL:


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

Opcionalmente, si esta asociación debe ser unidireccional, puede declarar la restricción NOT NULL en el mapeo de <key>:


<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
>

Por otro lado, si un hijo tiene múltiples padres, una asociación muchos-a-muchos es apropiada:


<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
>

Definiciones de tabla:

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

For more examples and a complete explanation of a parent/child relationship mapping, see Capítulo 22, Ejemplo: Padre/Hijo for more information.

En el próximo capítulo abordaremos los mapeos de asociaciones más complejas.