Mapeo de Colecciones
Colecciones persistentes
Hibernate requiere que los campos valuados en colección
persistentes sean declarados como un tipo de interface, por ejemplo:
La interface real podría ser java.util.Set,
java.util.Collection, java.util.List,
java.util.Map, java.util.SortedSet,
java.util.SortedMap o ... lo que te guste!
(Donde "lo que te guste" significa que tendrás que escribir una
implementación de org.hibernate.usertype.UserCollectionType.)
Nota cómo hemos inicializado la variable de instancia de
HashSet. 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 persist(), por ejemplo - Hibernate realmente
remplazará el HashSet con una instancia de una
implementación de Set propia de Hibernate.
Observa errores como este:
Las colecciones persistentes inyectadas por Hibernate se comportan
como HashMap, HashSet,
TreeMap, TreeSet o
ArrayList, dependiendo del tipo de interface.
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.
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).
Mapeos de colección
El elemento de mapeo de Hibernate usado para mapear una colección
depende del tipo de la interface. Por ejemplom un elemento
<set> se usa para mapear propiedades de tipo
Set.
]]>
Aparte de <set>, existen además
los elementos de mapeo <list>,
<map>, <bag>,
<array> y <primitive-array>.
El elemento <map> es representativo:
]]>
name el nombre de la propiedad de colección
table (opcional - por defecto al nombre de la propiedad)
el nombre de la tabla de coleciión (no usado para asociaciones
uno-a-muchos)
schema (opcional) el nombre de un esquema de tablas
para sobrescribir el esquema declarado en el elemento raíz
lazy (opcional - por defecto a true)
puede ser usado para deshabilitar la recuperación perezosa y
especificar que la asociación es siempre recuperada tempranamente
(no disponible para arrays)
inverse (opcional - por defecto a false)
marca esta colección como el extremo "inverso" de una asociación
bidireccional.
cascade (opcional - por defecto a none)
habilita operaciones en cascada a entidades hijas
sort (opcional) especifica una colección
con ordenamiento natural, o una clase comparadora
dada
order-by (opcional, sólo JDK1.4) especifica una columna
de tabla (o columnas) que definen el orden de iteración del
Map, Set o bag, junto a un
asc o desc opcional.
where (opcional) especifica una condición
WHERE 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)
fetch (opcional, por defecto a select)
Elige entre recuperación por unión externa (outer-join),
recuperar por selección secuencial, y recuperación por
subselección secuencial.
batch-size (opcional, por defecto a 1)
especifica un "tamaño de lote" para la recuperar
perezosamente instancias de esta colección.
access (opcional - por defecto a property):
La estrategia que debe usar Hibernate para acceder al valor de la
propiedad.
optimistic-lock (opcional - por defecto a true):
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.)
Claves foráneas de collección
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.
La columna clave de la colección es mapeada por el elemento
<key>.
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 not-null="true".
]]>
La restricción de clave foránea puede usar
ON DELETE CASCADE.
]]>
Mira el capítulo anterior por una definición completa del
elemento <key>.
Elementos de collección
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.
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 son usados para mapear
asociaciones de entidades.
Colecciones indexadas
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 que mapea a un índice de array, o
índice de List, o clave de Map.
El índice de un Map puede ser de cualquier tipo
básico, mapeado con <map-key>, o 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 usando el elemento <list-index>. La columna
mapeada contiene enteros secuenciales (numerados desde cero, por defecto).
]]>
column_name (requerido): El nombre de la columna que tiene
los valores índice de la colección.
base (opcional, por defecto a 0): El valor
de la columna índice que corresponde al primer elemento de la lista o
array.
]]>
column (opcional): El nombre de la columna que tiene
los valores índice de la colección.
formula (opcional): Una fórmula SQL usada para
evaluar la clave del mapa.
type (requerido): el tipo de las claves del mapa.
]]>
column (opcional): El nombre de la columna clave
foránea para los valores índice de la colección.
formula (opcional): Una fórmula SQL usada para
evaluar la clave foránea de la clave del mapa.
class (requerido): La clase de entidad usada como clave del mapa.
Si tu tabla no tiene una columna índice, y deseas aún usar List como
tipo de propiedad, debes 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 opcionalmente.
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.
Colecciones de valores y asociaciones muchos-a-muchos
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 índice.
Para una colección de valores, usamos la etiqueta <element>.
]]>
column (opcional): El nombre de la columna que tiene
los valores de los elementos de la colección.
formula (opcional): Una fórmula SQL usada para evaluar
el elemento.
type (requerido): El tipo del elemento de colección.
Una asociación muchos-a-muchos se especifica usando
el elemento <many-to-many>.
]]>
column (opcional): El nombre de la columna de clave
foránea del elemento.
formula (opcional): Una fórmula SQL opcional usada
para evaluar el valor de clave foránea del elemento.
class (requerido): El nombre de la clase asociada.
fetch (opcional - por defecto a join):
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 SELECT) de una entidad y sus relaciones
muchos-a-muchos a otras entidades, deberías habilitar la recuperación
join no sólo de la colección misma, sino también con este
atributo en el elemento anidado <many-to-many>.
unique (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.
not-found (opcional - por defecto a exception):
Especifica cómo serán manejadas las claves foráneas que referencian
filas perdidas: ignore tratará una fila perdida como
una asociación nula.
entity-name (opcional): El nombre de entidad de la clase
asociada, como una alternativa a class.
Algunos ejemplos, primero, un conjunto de cadenas:
]]>
Un bag conteniendo enteros (con un orden de iteración determinado por el
atributo order-by):
]]>
Un array de entidades - en este caso, una asociación muchos a muchos:
]]>
Un mapa de índices de cadenas a fechas:
]]>
Una lista de componentes (discutidos en el próximo capítulo):
]]>
Asociaciones uno-a-muchos
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.
]]>
class (requerido): El nombre de la clase asociada.
not-found (opcional - por defecto a 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.
entity-name (opcional): El nombre de entidad de la clase
asociada, como una alternativa a class.
Observa que el elemento <one-to-many> no necesita
declarar ninguna columna. Ni es necesario especificar el nombre de table
en ningún sitio.
Nota muy importante: Si la columna clave foránea de una asociación
<one-to-many> es declarada NOT NULL, debes
declarar el mapeo de <key> not-null="true"
o usar una asociación bidireccional con el mapeo de colección
marcado inverse="true". Ver la discusión sobre asociaciones
bidireccionales más adelante en este capítulo.
Este ejemplo muestra un mapa de entidades Part por nombre
(donde partName es una propiedad persistente de Part).
Observa el uso de un índice basado en fórmula.
]]>
Mapeos de colección avanzados
Colecciones ordenadas
Hibernate soporta colecciones implementando java.util.SortedMap y
java.util.SortedSet. Debes especificar un comparador en el fichero de
mapeo:
]]>
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 quieres que la misma base de datos ordene los elementos de colección usa el
atributo order-by de los mapeos set,
bag o map. Esta solución está disponible sólo
bajo el JDK 1.4 o superior (está implementado usando LinkedHashSet o
LinkedHashMap). Esto realiza la ordenación en la consulta SQL,
no en memoria.
]]>
Observa que 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 usando un filter() de colección.
Asociaciones bidireccionales
Una asociación bidireccional permite la nevegación desde
ambos "extremos" de la asociación. Son soportados dos tipos de asociación
bidireccional:
uno-a-muchos
set o bag valorados en un extremo, monovaluados al otro
muchos-a-muchos
set o bag valorados a ambos extremos
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 inverse (cuál de ellos es tu
elección, pero no puede ser una colección indexada).
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:
...
...
]]>
Los cambios hechos sólo al extremo inverso de la asociación no
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:
El lado no-inverso se usa para salvar la representación en memoria a la base de datos.
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 inverse="true".
....
....
]]>
Mapear un extremo de una asociación con inverse="true" no afecta
la operación de cascadas; éstos son conceptos ortogonales!
Asociaciones bidireccionales con colecciones indexadas
Requiere especial consideración una asociación bidireccional donde un extremo esté representado
como una <list> o <map>. Si hay una propiedad
de la clase hija que mapee a la columna índice, no hay problema, podemos seguir usando
inverse="true" en el mapeo de la colección:
....
....
]]>
Pero, si no existe tal proiedad en la clase hija, no podemos pensar en la asociación como
verdaderamente bidireccional (hay información en un extremo de la asociación que no está
disponible en el otro extremo). En este caso, no podemos mapear la colección con
inverse="true". En cambio, podríamos usar el siguiente mapeo:
....
....
]]>
Nota que, en este mapeo, el extremo de la asociación valuado en colección es responsable de las
actualizaciones a la clave foránea.
Asociaciones ternarias
Hay tres enfoques posibles para mapear una asociación ternaria.
Una es usar un Map con una asociación como su índice:
]]>
]]>
Un segundo enfoque es simplemente remodelar la asociación como una clase de entidad.
Este es el enfoque que usamos más comunmente.
Una alternativa final es usar elementos compuestos, que discutiremos más adelante.
Usando un <idbag>
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í podría 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.
El elemento <idbag> te permite mapear una List
(o Collection) con semántica de bag.
]]>
Como puedes ver, un <idbag> 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.
Observa que el rendimiento de actualización de un <idbag> es
mucho mejor que el de un <bag> regular!
Hibernate puede localizar filas individuales eficientemente y actualizarlas o borrarlas
individualmente, igual que si fuese una lista, mapa o conjunto.
En la implementación actual, la estrategia de generación de identificador
native no está soportada para identificadores de colecciones
<idbag>.
Ejemplos de colección
Las secciones previas son bastantes confusas. Así que miremos un ejemplo.
Esta clase:
tiene una colección de instancias de Child.
Si cada hijo tiene como mucho un padre, el mapeo más natural es
una asociación uno-a-muchos:
]]>
Esto mapea a las siguientes definiciones de tablas:
Si el padre es requerido, usa una asociación bidireccional
uno-a-muchos:
]]>
Observa la restricción NOT NULL:
Alternativamente, si absolutamente insistes que esta asociación debe ser unidireccional,
puedes declarar la restricción NOT NULL en el mapeo de
<key>:
]]>
En la otra mano, si un hijo pudiera tener múltiples padres, sería apropiada
una asociación muchos-a-muchos:
]]>
Definiciones de tabla:
Para más ejemplos y un paseo completo a través del mapeo de relaciones padre/hijo,
ver .
Son posibles mapeos de asociación aún más complejos. Catalogaremos todas las posibilidades
en el próximo capítulo.