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.