Hibernate.orgCommunity Documentation
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).
Hay bastantes rangos de mapeos que se pueden generar para colecciones que cubren muchos modelos relacionales comúnes. Le recomendamos que experimente con la herramienta de generación de esquemas para comprender cómo se traducen varias declaraciones de mapeo a tablas de bases de datos.
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="propertyName" table="tab
le_name" schema="sc
hema_name" lazy="true
|extra|false" inverse="t
rue|false" cascade="a
ll|none|save-update|delete|all-delete-orphan|delete-orphan" sort="unso
rted|natural|comparatorClass" order-by="
column_name asc|desc" where="arb
itrary sql where condition" fetch="joi
n|select|subselect" batch-size
="N" access="fi
eld|property|ClassName" optimistic
-lock="true|false" mutable="t
rue|false" node="element-name|." embed-xml="true|false" > <key .... /> <map-key .... /> <element .... /> </map >
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
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 columna clave de colección o columnas de la tabla de colección. El elemento <key>
mapea la columna clave de la colección.
Puede haber una restricción de nulabilidad sobre la columna de clave foránea. Para la mayoría de las colecciones, esto es implícito. Para asociaciones unidireccionales uno-a-muchos, la columna de clave foránea es nulable por defecto, de modo que puede que necesite especificar not-null="true"
.
<key column="productSerialNumber" not-null="true"/>
La restricción de clave foránea puede utilizar ON DELETE CASCADE
.
<key column="productSerialNumber" on-delete="cascade"/>
Vea el capítulo anterior para obtener una definición completa del elemento <key>
.
Las colecciones pueden contener casi cualquier tipo de Hibernate, incluyendo: los tipos básicos, personalizados, componentes y referencias a otras entidades. Esta es una diferencia 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ólamente el "enlace" entre los dos objetos se considera como un estado mantenido por la colección.
Se hace referencia al tipo contenido como el tipo de elemento de la colección. Los elementos de colección son mapeados por <element>
o <composite-element>
, o en el caso de referencias de entidades, con <one-to-many>
o <many-to-many>
. Las dos primeras mapean elementos con semántica de valor, los dos siguientes se utilizan para mapear asociaciones de entidades.
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="column_name" base="
0|1|..."/>
| |
|
<map-key column="column_name" formul
a="any SQL expression" type="
type_name" node="@attribute-name" length="N"/>
| |
| |
|
<map-key-many-to-many column="column_name" formul
a="any SQL expression" class="ClassName" />
| |
| |
|
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="column_name" formul
a="any SQL expression" type="
typename" length="L" precision="P" scale="S" not-null="true|false" unique="true|false" node="element-name" />
| |
| |
|
A many-to-many association is specified using the <many-to-many>
element.
<many-to-many column="column_name" formul
a="any SQL expression" class=
"ClassName" fetch=
"select|join" unique
="true|false" not-fo
und="ignore|exception" entity
-name="EntityName" proper
ty-ref="propertyNameFromAssociatedClass" node="element-name" embed-xml="true|false" />
| |
| |
| |
| |
| |
| |
| |
|
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 instancia de la clase entidad contenida no puede pertenecer a más de una instancia de la colección.
Una instancia de la clase entidad contenida no puede aparecer en más de un valor del índice de colección.
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="ClassName" not-fo
und="ignore|exception" entity
-name="EntityName" node="element-name" embed-xml="true|false" />
| |
| |
|
El elemento <one-to-many>
no necesita declarar ninguna columna. Ni es necesario especificar el nombre de table
en ningún sitio.
Si la columna de la clave foránea de una asociación <one-to-many>
se declara NOT NULL
, debe declarar el mapeo de <key>
not-null="true"
o utilizar una asociación bidireccional con el mapeo de colección marcado inverse="true"
. Vea la discusión sobre asociaciones bidireccionales más adelante en este capítulo para obtener mayor información.
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
>
El valor del atributo order-by
es una ordenación SQL, no una ordenación HQL.
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:
conjunto o bag valorados en un lado, monovaluados en el otro
set o bag valorados en ambos extremos
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.
Hay tres enfoques posibles para mapear una asociación ternaria. Una es utilizar un Map
con una asociación como su índice:
<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
>
<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
>
Un segundo enfoque es remodelar la asociación como una clase de entidad. Este es el enfoque más común.
La última opción es utilizar elementos compuestos que discutiremos más adelante.
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.
Copyright © 2004 Red Hat, Inc.