Introducción a Hibernate
Prefacio
Este capítulo es un tutorial introductorio de Hibernate. Comenzamos con
una aplicación simple de línea de comandos usando un base de datos
en-memoria y desarrollándola en fácil para entender los pasos.
Este tutorial está concebido para usuarios nuevos de Hibernate pero
requiere conocimiento en Java y SQL. Está basado en un tutorial de
Michael Gloegl. Las bibliotecas de terceros que mencionamos son para JDK 1.4
y 5.0. Podrías necesitar otras para JDK 1.3.
Parte 1 - La primera Aplicación Hibernate
Primero, crearemos una aplicación simple de Hibenate basada en consola.
Usamos usamos una base de datos en-memoria (HSQL DB), de modo que no necesitamos
instalar ningún servidor de base de datos.
Asumamos que necesitamos una aplicación pequeña de base de datos que
pueda almacenar eventos que queremos atender, e información acerca de los
hostales de estos eventos.
La primera cosa que hacemos, es armar nuestro directorio de desarrollo y poner
en él todas las bibliotecas Java que necesitamos. Descarga la distribución
de Hibernate del sitio web de Hibernate. Extrae el paquete y coloca todas las
bibliotecas requeridas encontradas en /lib dentro del directorio
/lib de nuestro nuevo directorio de desarrollo de trabajo.
Debe asemejarse a esto:
Este es el conjunto mínimo de bibliotecas requeridas para Hibernate (observa que
también hemos copiado hibernate3.jar, el fichero principal). Ver el fichero
README.txt en el directorio lib/ de la distribución
de Hibernate para más información sobre bibliotecas de terceros requeridas y
opcionales. (Realmente, Log4J no es requerida aunque preferida por muchos desarrolladores).
Por siguiente, creamos una clase que represente el evento que queremos
almacenar en base de datos.
La primera clase
Nuestra primera clase persistente es un JavaBean simple con algunas propiedades:
Puedes ver que esta clase usa las convenciones de nombrado estándar de JavaBean
para métodos getter y setter de propiedad, así como visibilidad privada
para los campos. Esto es un diseño recomendado, aunque no requerido. Hibernate
también puede acceder a los campos directamente; el beneficio de los métodos
de acceso es la robustez para la refactorización.
La propiedad id tiene un valor único de identificador para
un evento en particular. Todas las clase de entidad persistentes ( también hay
clases dependientes menos importantes) necesitarán una propiedad identificadora
similar si queremos usar el conjunto completo de funcionalidades de Hibernate. De hecho,
la mayoría de las aplicaciones (esp. aplicaciones web) necesitan distinguir
objetos por identificador, de modo que debes considerar esto como un aspecto en vez de una
limitación. Sin embargo, usualmente no manipulamos la identidad de un objeto, por
lo tanto el método setter debe ser privado. Sólo Hibernate asignará
identificadores cuando un objeto sea salvado. Puedes ver que Hibernate puede acceder a
métodos de acceso públicos, privados y protegidos, tanto como directamente a
campos (públicos, privados y protegidos). La elección está en ti,
y puedes ajustarla a tu diseño de aplicación.
El constructor sin argumentos es un requerimiento para todas las clases persistentes.
Hibernate tiene que crear objetos para ti, usando reflección Java. El constructor
puede ser privado, sin embargo, la visibilidad de paquete es requerida para la generación
de proxies en tiempo de ejecución y la recuperación de datos sin
instrumentación del bytecode.
Coloca este fichero de código Java en un directorio llamado src
en la carpeta de desarrollo. El directorio ahora debe verse como esto:
+src
Event.java]]>
En el próximo paso, le decimos a Hibernate sobre esta clase persistente.
El fichero de mapeo
Hibernate necesita saber cómo cargar y almacenar objetos de la
clase persistente. Aquí es donde el fichero de mapeo de Hibernate
entra en juego. El fichero de mapeo le dice a Hibernate a qué tabla en
la base de datos tiene que acceder, y qué columnas en esta tabla debe usar.
La estructura básica de un fichero de mapeo se parece a esto:
[...]
]]>
Observa que el DTD de Hibernate es muy sofisticado. Puedes usarlo para
autocompleción de los elementos y atributos XML de mapeo en tu
editor o IDE. Debes también abrir el fichero DTD en tu editor de
texto. Es la forma más fácil para tener un panorama de todos
los elementos y atributos y ver los valores por defectos, así como
algunos comentarios. Nota que Hibernate no cargará el fichero DTD de
la web, sino que primero buscará en el classpath de la aplicación.
El fichero DTD está incluído en hibernate3.jar
así como también en el directorio src/ de la
distribución de Hibernate.
Omitiremos la declaración de DTD en futuros ejemplos para acortar
el código. Por supuesto, no es opcional.
Entre las dos etiquetas hibernate-mapping, incluye
un elemento class. Todas las clases de entidad
persistentes (de nuevo, podría haber más adelante clases
dependientes, que no sean entidades de-primera-clase) necesitan dicho mapeo
a una tabla en la base de datos SQL:
]]>
Hasta ahora dijimos a Hibernate cómo persistir y cargar el objeto
de clase Event a la tabla EVENTS,
cada instancia representada por una fila en esta tabla. Ahora continuamos con
un mapeo de la propiedad de identificado único a la clave primaria
de la tabla. Además, como no queremos cuidar del manejo de este identificador,
configuramos la estrategia de generación de identificadores para una columna
clave primaria delegada:
]]>
El elemento id el la declaración de la propiedad
identificadora, name="id" declara el nombre de la
propiedad Java. Hibernate usará los métodos getter y setter
para acceder a la propiedad. El attributo de columna dice a Hibernate cuál
columna de la tabla EVENTS usamos para esta clave primaria.
El elemento anidado generator especifica la estrategia de
generación de identificadores, en este caso usamos increment,
que es un método muy simple de incremento de número en-memoria
útil mayormente para testeo (y tutoriales). Hibernate también
soporta identificadores generados por base de datos, globalmente únicos,
así como también asignados por aplicación (o cualquier
estrategia para la que hayas escrito una extensión).
Finalmente incluímos declaraciones para las propiedades persistentes
de la clases en el fichero de mapeo. Por defecto, ninguna propiedad de la clase
se considera persistente:
]]>
Al igual que con el elemento id, el atributo name
del elemento property dice a Hibernate cáles métodos
getter y setter usar.
¿Por qué el mapeo de la propiedad date
incluye el atributo column, pero el de la de
title no? Sin el atributo column
Hibernate usa por defecto el nombre de propiedad como nombre de columna.
Esto funciona bien para title. Sin embargo,
However, date es una palabra reservada en la
mayoría de las bases de datos, así que mejor la mapeamos
a un nombre diferente.
La próxima cosa interesante es que el mapeo de title
carece de un atributo type. Los tipos que declaramos y usamos
en el fichero de mapeo no son, como podrías esperar, tipos de datos Java.
Tampoco son tipos de base de datos SQL. Estos tipos son los llamados así
Tipos de mapeo de Hibernate, convertidores que pueden
traducir de tipos Java a SQL y vice versa. De nuevo, Hibernate intentará
determinar la conversión y el mapeo mismo de tipo correctos si el atributo
type no estuviese presente en el mapeo. En algunos casos esta
detección automática (usando reflección en la clase Java)
puede no tener lo que esperas o necesitas. Este es el caso de la propiedad
date. Hibernate no puede saber is la propiedad mapeará
a una columna date, timestamp o
time. Declaramos que queremos preservar la información
completa de fecha y hora mapeando la propiedad con un timestamp.
Este fichero de mapeo debe ser salvado como Event.hbm.xml,
justo en el directorio próximo al fichero de código fuente de
la clase Java Event. El nombrado de los ficheros de mapeo
puede ser arbitrario, sin embargo, el sufijo hbm.xml se ha
vuelto una convención el la comunidad de desarrolladores de Hibernate.
La estructura de directorio debe ahora verse como esto:
+src
Event.java
Event.hbm.xml]]>
Continuamos con la configuración principal de Hibernate.
Configuración de Hibernate
Tenemos ahora una clase persistente y su fichero de mapeo en su sitio. Es momento de
configurar Hibernate. Antes que hagamos esto, necesitaremos una base de datos.
HSQL DB, un DBMS SQL en-memoria basado en Java, puede ser descargado del sitio web
de HSQL DB. Realmente, de esta descarga sólo necesitas el hsqldb.jar.
Coloca este fichero en el directorio lib/ de la carpeta de desarrollo.
Crea un directorio llamado data en la raíz del directorio de
desarrollo. Allí es donde HSQL DB almacenará sus ficheros de datos.
Hibernate es la capa en tu aplicación que se conecta a esta base de datos,
de modo que necesita información de conexión. Las conexiones se hacen
a través de un pool de conexiones JDBC, que tambén tenemos que configurar.
La distribución de Hibernate contiene muchas herramientas de pooling de conexiones
JDBC de código abierto, pero para este tutorial usaremos el pool de conexiones
prefabricado dentro de Hibernate. Observa que tienes que copiar la biblioteca requerida
en tu classpath y usar diferentes configuraciones de pooling de conexiones si quieres
usar un software de pooling JDBC de terceros de calidad de producción.
Para la configuración de Hibernate, podemos usar un fichero
hibernate.properties simple, un fichero hibernate.cfg.xml
ligeramente más sofisticado, o incluso una configuración completamente
programática. La mayoría de los usuarios prefieren el fichero de
configuración XML:
org.hsqldb.jdbcDriver
jdbc:hsqldb:data/tutorial
sa
1
org.hibernate.dialect.HSQLDialect
true
create
]]>
Observa que esta configuración XML usa un DTD diferente.
Configuramos la SessionFactory de Hibernate, una
fábrica global responsable de una base de datos en particular.
Si tienes varias bases de datos, usa varias configuraciones
<session-factory> , usualmente en varios
ficheros de configuración (para un arranque más fácil).
Los primeros cuatro elementos property contienen la configuración
necesaria para la conexión JDBC. El elemento de dialecto property
especifica la variante de SQL en particular que genera Hibernate. La opción
hbm2ddl.auto activa la generación automática de esquemas
de base de datos, directamente en la base de datos. Esto, por supuesto, puede desactivarse
(quitando la opción config) o redirigido a un fichero con la ayuda de la tarea
de Ant SchemaExport. Finalmente, agregamos el(los) fichero(s) de mapeo
para clases persistentes.
Copia este fichero dentro del directorio de código fuente, de modo que
termine ubicado en la raiíz del classpath. Hibernate busca automáticamente
un fichero llamado hibernate.cfg.xml en la raíz del classpath
al arrancar.
Construyendo con Ant
Construiremos ahora el tutorial con Ant. Necesitarás tener Ant instalado.
Obténlo de Página
de descarga de Ant. No se cubrirá aquí cómo instalar Ant.
Por favor refiérete al
Manual de Ant. Después que hayas instalado Ant, podemos comenzar a
crear el buildfile. Será llamado build.xml y colocado
directamente en el directorio de desarrollo.
Reparar Ant
Observa que la distribución de Ant está por defecto rota
(como se describe en el FAQ de Ant) y tiene que ser reparado por ti,
por ejemplo, si quisieras usar JUnit desde dentro de tu fichero de construcción.
Para hacer que funcione la tarea de JUnit (no lo necesitaremos en este tutorial),
copia junit.jar a ANT_HOME/lib o quita el trozo de plugin
ANT_HOME/lib/ant-junit.jar.
Un fichero de construcción básico se ve como esto:
]]>
Esto dirá a Ant que agregue todos los ficheros en el directorio lib que terminen con
.jar al classpath usado para la compilación. También copiará
todos los ficheros que no sean código Java al directorio objetivo, por ejemplo,
ficheros de configuración y mapeos de Hibernate. Si ahora corres Ant, debes obtener
esta salida:
ant
Buildfile: build.xml
copy-resources:
[copy] Copying 2 files to C:\hibernateTutorial\bin
compile:
[javac] Compiling 1 source file to C:\hibernateTutorial\bin
BUILD SUCCESSFUL
Total time: 1 second ]]>
Arranque y ayudantes
Es momento de cargar y almacenar algunos objetos Event,
pero primero tenemos que completar la configuración de algún
código de infraestructura. Tenemos que arrancar Hibernate. Este
arranque incluye construir un objeto SessionFactory global
y almacenarlo en algún sitio de fácil acceso en el código
de aplicación. Una SessionFactory puede abrir nuevas
Session's. Una Session representa un unidad
de trabajo mono-hebra. La SessionFactory es un objeto global
seguro entre hebras, instanciado una sola vez.
Crearemos una clase de ayuda HibernateUtil que cuide del
arranque y haga conveniente el manejo de Session.
El así llamado patrón Sesión de Hebra Local
(ThreadLocal Session) es útil aquí; mantenemos la unidad
de trabajo actual asociada a la hebra actual. Echemos una mirada a la implementación:
Esta clase no ólo produce la SessionFactory global en
su inicializador static (llamado sólo una vez por la JVM al cargar la clase),
sino que también tiene una variable ThreadLocal para
tener la Session para la hebra actual. No importa cuándo
llames a HibernateUtil.currentSession(), siempre devolverá
la misma unidad de trabajo de Hibernate en la misma hebra. Una llamada a
HibernateUtil.closeSession() termina la unidad de trabajo actualmente
asociada a la hebra.
Asegúrate de entender el concepto Java de una variable local a una hebra antes
de usar esta ayuda. Una clase HibernateUtil más potente puede
encontrarse en CaveatEmptor, http://caveatemptor.hibernate.org/,
así como en el libro "Hibernate in Action". Observa que esta clase no es necesaria
si despliegas Hibernate en un servidor de aplicaciones J2EE: una Session
será automáticamente ligada a la transacción JTA actual, y puedes
buscar la SessionFactory a través de JNDI. Si usas JBoss AS,
Hibernate puede ser desplegado como un servicio de sistema manejado y automáticamente
ligará la SessionFactory a un nombre JNDI.
Coloca HibernateUtil.java en el directorio de fuentes de desarrollo,
junto a Event.java:
+src
Event.java
Event.hbm.xml
HibernateUtil.java
hibernate.cfg.xml
+data
build.xml]]>
Esto también debe compilar sin problemas. Finalmente necesitamos configurar
un sistema de logging (registro). Hibernate usa commons logging y te deja la elección
entre Log4J y logging de JDK 1.4. La mayoría de los desarrolladores prefieren
Log4J: copia log4j.properties de la distribución de Hibernate
(está en el directorio etc/) a tu directorio src,
junto a hibernate.cfg.xml. Echa una mirada a la configuración de
ejemplo y cambia los ajustes si te gusta tener una salida más verborrágica.
Por defecto, sólo se muestra el mensaje de arranque de Hibernate en la salida.
La infraestructura del tutorial está completa, y estamos listos para hacer
algún trabajo real con Hibernate.
Cargando y almacenando objetos
Finalmente, podemos usar Hibernate para cargar y almacenar objetos.
Escribimos una clase EventManager con un método
main():
Leemos algunos argumentos de la línea de comandos, y si el primer
argumento es "store", creamos y almacenamos un nuevo Event:
Creamos un nuevo objeto Event, y se lo damos a Hibernate.
Hibernate cuida ahora del SQL y ejecuta INSERTs en la base
de datos. Echemos una mirada al código de manejo de Session
y Transaction antes de ejecutar esto.
Una Session es una sola unidad de trabajo. Podría sorprenderte
que tengamos una API adicional, Transaction. Esto implica que una unidad
de trabajo puede ser "más larga" que una sola transacción de base de datos;
imagina una unidad de trabajo que se abarca varios ciclos petición/respuesta HTTP
(por ejemplo, un diálogo asistente) en una aplicación web. Separar las
transacciones de base de datos de "las unidades de trabajo de la aplicación desde
el punto de vista del usuario" es uno de los conceptos básicos de diseño de
Hibernate. Llamamos una unidad de trabajo larga Transacción de
Aplicación, usualmente encapsulando varias transacciones de base de
datos más cortas. Por ahora mantendremos las cosas simples y asumiremos una
granularidad uno-a-uno entre una Session y una Transaction.
¿Qué es lo que hacen Transaction.begin() y commit()?
¿Dónde está el rollback en caso que algo vaya mal? La API de Transaction
de Hibernate es opcional realmente, pero la usamos por conveniencia y portabilidad. Si manejases
la transacción de base de datos por ti mismo (por ejemplo, llamando a
session.connection.commit()), ligarías el código a un entorno
de despliegue particular, en este JDBC directo no manejado. Estableciendo la fábrica
de Transaction en tu configuración de Hibernate puedes desplegar
tu capa de persistencia en cualquier sitio. Echa una mirada al
para más información sobre manejo y demarcación de transacciones.
Hemos saltado también cualquier manejo de excepciones y rollback en este ejemplo.
Para ejecutar la primera rutina tenemos que agregar un objetivo llamable al fichero
de construcción de Ant:
]]>
El valor del argumento action es establecido por línea de
comandos al llamar al objetivo:
ant run -Daction=store]]>
Debes ver, después de la compilación, a Hibernate arrancando y, dependiendo
de tu configuración mucha salida de registro (log). Al final encontrarás
la siguiente línea:
Esta es la INSERT ejecutada por Hibernate, los signos de preguntas
representan parámetros de ligado JDBC. Para ver los valores ligados como
argumentos, o para reducir la verborragia del registro, chequea tu
log4j.properties.
Ahora quisiéramos listar acontecimientos almacenados también,
así que agregamos una opción al método principal:
Agregamos también un nuevo método listEvents():
Lo que hacemos aquí es usar una consulta HQL (Lenguaje de Consulta de Hibernate
o Hibernate Query Language) para cargar todos los objetos Event
existentes de la base de datos. Hibernate generará el SQL apropiado, lo enviará
a la base de datosy poblará los objetos Event con datos.
Puedes, por supuesto, crear consultas más complejas con HQL.
Si ahora llamas a Ant con -Daction=list, debes ver los eventos
que has almacenado hasta ahora. Puede sorprenderte que esto no funcione, al menos
si has seguido este tutorial paso por paso; el resultado siempre estará
vacío. La razon de esto es la opción hbm2ddl.auto
en la configuración de Hibernate: Hibernate recreará la base de datos
en cada ejecución. Deshabilítala quitando la opción, y verás
resultados en tu listado después que llames a la acción store
unas cuantas veces. La generación y exportación de esquema es útil
mayormente en testeo unitario.
Part 2 - Mapeando asociaciones
Hemos mapeado un clase de entidad persistente a una tabla. Construyamos sobre esto y agreguemos
algunas asociaciones de clase. Primero agregaremos personas a nuestra aplicación,
y almacenaremos una lista de eventos en las que participan.
Mapeando la clase Person
El primer corte de la clase Person es simple:
Crea un fichero de mapeo llamado Person.hbm.xml:
]]>
Finalmente, agrega el nuevo mapeo a la configuración de Hibernate:
]]>
Crearemos ahora una asociación entre estas dos entidades. Obviamente,
las personas pueden participar en eventos, y los eventos tienen participantes.
Las cuestiones de diseño con que tenemos que tratar son: direccionalidad,
multiplicidad y comportamiento de colección.
Una asociación unidireccional basada en Set
Agregaremos una colección de eventos a la clase Person.
De esta forma podemos navegar facilmente a los eventos de una persona en particular,
sin ejecutar una consulta explícita, llamando a aPerson.getEvents().
Usamos una colección Java, un Set, porque la colección no
contendrá elementos duplicados y el ordenamiento no nos es relevante.
Hasta ahora hemos diseñado asociaciones unidireccionales multivaluadas, implementadas con un
Set. Escribamos el código para esto en las clases Java y luego lo
mapeemos:
Antes que mapeemos esta asociación, piensa sobre el otro lado. Claramente, podemos
mantener esto solamente unidireccional. O podemos crear otra colección en el
Event, si queremos ser capaces de navegarlos bidireccionalmente;
por ejemplo, anEvent.getParticipants(). Esta es una elección
de diseño que recae en ti, pero lo que está claro de esta discusión
es la multiplicidad de la asociación: "multi" valuada a ambos lados, llamamos a esto
una asociación muchos-a-muchos. Por lo tanto, usamos un mapeo
many-to-many de Hibernate:
]]>
Hibernate soporta todo tipo de mapeos de colección, siendo el más común
un <set>. Para una asociación muchos-a-muchos (o relación
de entidad n:m), se necesita una tabla de asociación. Cada fila en esta
tabla representa un enlace entre una persona y un evento. Esta tabla se configura con el atributo
table del elemento set. El nombre de la columna identificadora
en la asociación, para el lado de la persona, se define con el elemento
<key>. El nombre de columna para el lado del evento se define con el atributo
column del <many-to-many>. También tienes que decirle
a Hibernate la clase de los objetos en tu colección (correcto: la clase del otro lado de la
colección de referencias).
El esquema de base de datos para este mapeo es, por lo tanto:
| *EVENT_ID | | |
| EVENT_DATE | | *PERSON_ID | <--> | *PERSON_ID |
| TITLE | |__________________| | AGE |
|_____________| | FIRSTNAME |
| LASTNAME |
|_____________|
]]>
Trabajando la asociación
Traigamos alguna gente y eventos juntos en un nuevo método en
EventManager:
Después de cargar una Person y un Event, simplemente
modifica la colección usando sus métodos normales. Como puedes ver, no hay una llamada
explícita a update() o save(). Hibernate detecta
automáticamente que la colección ha sido modificada y necesita ser salvada. Esto
es llamado chequeo sucio automótico (automatic dirty checking), y
también puedes intentarlo modificando el nombre de la propiedad de fecha de cualquiera de tus
objetos. Mientras estén en estado persistente, esto es, ligados a una
Session de Hibernate particular (es decir, justo han sido cargados o almacenados
en una unidad de trabajo), Hibernate monitoriza cualquier cambio y ejecuta SQL en estilo
escribe-por-detrás. El proceso de sincronización del estado de memoria con la base
de datos, usualmente sólo al final de una unidad de trabajo,
es llamado limpieza (flushing).
Podrías, por supuesto, cargar persona y evento en unidades de trabajo diferentes. O
modificas un objeto fuera de una Session, cuando no está en estado
persistente (si antes era persistente llamamos a este estado separado (detached)
). En código (no muy realista), esto se vería como sigue:
La llamada a update hace a un objeto persistente de nuevo, podrías
decir que la liga a una nueva unidad de trabajo, de modo que cualquier modificación que
le hagas mientras esté separado puede ser salvada a base de datos.
Bueno, esto no es muy usado en nuestra situación actual, pero es un concepto
importante que puedes diseñar en tu propia aplicación. Por ahora, completa
este ejercicio agregando una nueva acción al método main de
EventManager y llámala desde la línea de comandos.
Si necesitas los identificadores de una persona o evento, el método
save() los devuelve.
Esto fue un ejemplo de una asociación entre dos clases igualmente importantes, dos entidades.
Como se ha mencionado anteriormente, hay otras clases y tipos en un modelo típico,
usualmente "menos importantes". Algunos ya los habrás visto, como un int
o un String. Llamamos a estas clases tipos de valor (value types),
y sus instancias dependen de una entidad en particular. Las instancias de estos
tipos no tienen su propia identidad, ni son compartidas entre entidades (dos personas no referencian
el mismo objeto firstname, incluso si tuvieran el mismo primer nombre). Por supuesto,
los tipos de valor no sólo pueden encontrarse en el JDK (de hecho, en una aplicación
Hibernate todas las clases del JDK son consideradas tipos de valor), sino que además puedes
escribir por ti mismo clases dependientes, por ejemplo, Address o
MonetaryAmount.
También puedes diseñar una colección de tipos de valor. Esto es conceptualmente
muy diferente de una colección de referencias a otras entidades, pero se ve casi lo mismo en
Java.
Colección de valores
Agregamos una colección de objetos tipificados en valor a la entidad Person.
Queremos almacenar direcciones de email, de modo que el tipo que usamos es String,
y la colección es nuevamente un Set:
El mapeo de este Set:
]]>
La diferencia comparada con el mapeo anterior es la parte element, que le dice
a Hibernate que la colección no contiene referencias a otra entidad, sino una colección
de elementos de tipo String (el nombre en minúsculas te dice que es un
tipo/conversor de mapeo de Hibernate). Una vez más, el atributo table del
elemento set determina el nombre de la tabla para la colección. El elemento
key define el nombre de la columna clave foránea en la tabla de colección.
El atributo column en el elemento element define el nombre de
columna donde realmente serán almacenados los valores String.
Echa una mirada al esquema actualizado:
| *EVENT_ID | | | |___________________|
| EVENT_DATE | | *PERSON_ID | <--> | *PERSON_ID | <--> | *PERSON_ID |
| TITLE | |__________________| | AGE | | *EMAIL_ADDR |
|_____________| | FIRSTNAME | |___________________|
| LASTNAME |
|_____________|
]]>
Puedes ver que la clave primaria de la tabla de colección es de hecho una clave
compuesta, usando ambas columnas. Esto implica también que no pueden haber
direcciones de email duplicadas por persona, que es exactamente la semántica
que necesitamos para un conjunto en Java.
Puedes ahora intentar y agregar elementos a esta colección, al igual que
hicimos antes enlazando personas y eventos. Es el mismo código en Java.
Asociaciones bidireccionales
A continuacion vamos a mapear una asociación bidireccional, haciendo que la
asociación entre persona y evento funcione desde ambos lados en Java. Por supuesto,
el esquema de base de datos no cambia; todavía necesitamos multiplicidad muchos-a-muchos.
Una base de datos relacional es más flexible que un lenguaje de programación
de red, así que no necesita nada parecido a una dirección de navegación;
los datos pueden ser vistos y recuperados en cualquier forma posible.
Primero agrega una colección de participantes a la clase de eventos
Event:
Ahora mapea este lado de la asociación también, en
Event.hbm.xml.
]]>
Como ves, estos son mapeos normales de set en ambos documentos de
mapeo. Nota que los nombres de columnas en key y
many-to-many fueron permutados en ambos documentos de mapeo. Aquí la
adición más importante es el atributo inverse="true" en el
elemento set del mapeo de colección de Event.
Lo que esto significa es que Hibernate debe tomar el otro lado - la clase Person -
cuando necesite descubrir información sobre el enlace entre las dos. Esto será mucho
más fácil de entender una vez que veas cómo se crea el enlace bidireccional
entre nuestras dos entidades.
Trabajando enlaces bidireccionales
Primero, ten en mente que Hhibernate no afecta la semántica normal de Java. ¿Cómo
hemos creado un enlace entre una Person y un Event en el
ejemplo unidireccional? Hemos agregado una instancia de Event a la colección
de referencias de eventos de una instancia de Person. De modo que, obviamente,
si queremos que este enlace funcione bidireccionalmente, tenemos que hacer lo mismo del otro lado,
agregando una referencia a Person a la colección en un Event.
Este "establecer el enlace a ambos lados" es absolutamente necesario y nunca debes olvidar hacerlo.
Muchos desarrolladores programan a la defensiva y crean métodos de
manejo de un enlace para establecer correctamente ambos lados, por ejemplo
en Person:
Nota que los métodos get y set para esta colección son ahora protegidos. Esto le
permite a clases en el mismo paquete y a subclases acceder aún a los métodos, pero
previene a cualquier otro de ensuciarse con la colección directamente (bueno, casi).
Probablemente debas hacer lo mismo con la colección al otro lado.
Y ¿qué del atributo de mapeo inverse? Para ti, y para Java, un enlace
bidireccional es simplemente cuestión de establecer correctamente las referencias a ambos
lados. Hibernate, sin embargo, no tiene suficiente información para arreglar correctamente
sentencias INSERT y UPDATE de SQL (para evitar violación
de restricciones), y necesita alguna ayuda para manejar asociaciones bidireccionales apropiadamente.
El hacer un lado de la asociación inverse le dice a Hibernate que basicamente
lo ignore, que lo considere un espejo del otro lado. Esto es todo lo necesario para
que Hibernate resuelva todas las incidencias al transformar un modelo de navegación direccional a
un esquema SQL de base de datos. Las reglas que tienes que recordar son directas: Todas las asociaciones
bidireccionales necesitan uno de los lados como inverse. En una asociación
uno-a-muchos debe ser el lado-de-muchos. En una asociación muchos-a-muchos, puedes tomar cualquier
lado, no hay diferencia.
Summary
Este tutorial cubrió los fundamentos de escribir una simple aplicación independiente
de Hibernate.
Si ya te sientes confidente con Hibernate, continúa navegando a través de la
tabla de contenidos de la documentación de referencia para los temas que encuentres
interesantes. Los más consultados son procesamiento transaccional (),
rendimiento de recuperación (), o el uso de la API
() y las funcionalidades de consulta ().
No olvides chequear el sitio web de Hibernate por más (especializados) tutoriales.