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. <literal>Usando un <idbag></literal> 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.