Ficheros de mapeo de Hibernate desde las clases
Introducción
En tutoriales anteriores hemos visto que, usando anotaciones, podemos crear clases que mapeen las entidades de una base de datos sin necesidad
de ficheros de configuración (del tipo «miEntidad.hbm.xml»).
Aunque esto es muy práctico, tiene el «inconveniente» (por ponerle pegas) de que las anotaciones sólo son válidas en entornos que empleen
Java 5 o superiores… Por tanto puede darse el caso de que una aplicción que hemos diseñado con anotaciones tenga que ser migrada para adaptarla
a un cliente que usa en su entorno Java 1.4 (y al que no podamos convencer para cambiar de versión de Java ;).
Esto supondría que tendríamos que eliminar del código todas las anotaciones y «picar» todo el código de los ficheros de mapeo de entidades.
En este tutorial vamos a intentar generar los ficheros de configuración de Hibernate de manera automática a partir de las clases anotadas, usando
para ello las Hibernate Tools
Aspectos como la descarga e instalación de Hibernate Tools quedan fuera de este tutorial, pues hay otro (arriba tenéis el enlace) en el que se
explica todo el proceso
Sí, sí, pero… ¿cómo se hace?
Una vez que hemos descargado las Hibernate Tools, tenemos dos opciones. Podemos usarlas como plugin de Eclipse o podemos usarlas mediante Ant
Como plugin de Eclipse
Una vez que tenemos las tools instaladas y la consola de configuración de Hibernate generada, vamos a usar dicha consola para hacer la magia…
Pinchamos en la flecha junto al botón adecuado de la barra de herramientas superior (el que está recuadrado)
En el desplegable que aparece, seleccionamos «Open Hibernate Code Generation Dialog», y se nos abre la ventana siguiente:
Pulsamos el botón de «Nueva configuración»:
En la ventana que aparece (pestaña «Main»), rellenamos el nombre de la nueva configuración, la configuración de consola que vamos a usar
y el directorio en el que se almacenarán los ficheros generados
En la pestaña «Exporters» marcamos la opción «Hibernate XML Mappings» y pulsamos el botón «Run»… y la magia está hecha
Abrimos uno de los ficheros generados:
<?xml version="1.0"?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd"> <hibernate-mapping> <class name="com.autentia.store.User" table="User"> <id name="id" type="java.lang.Integer" access="field"> <column name="id" /> <generator class="native"></generator> </id> <property name="address" type="java.lang.String" access="field"> <column name="address" /> </property> <property name="creditCard" type="java.lang.String" access="field"> <column name="creditCard" /> </property> <property name="email" type="java.lang.String" access="field"> <column name="email" /> </property> <property name="name" type="java.lang.String" access="field"> <column name="name" /> </property> <property name="password" type="java.lang.String" access="field"> <column name="password" /> </property> </class> </hibernate-mapping>
Usando Ant
Ya hemos visto que, usando el plugin de Eclipse, la cosa es bastante sencilla… Ahora vamos a ver cómo se podría hacer lo mismo con Ant.
Esta manera de resolver el problema nos da la ventaja de no necesitar Eclipse… un fichero BAT o un shell script podría lanzar automáticamente
el proceso de creación.
Lo primero que necesitamos es un fichero ejecutable de ant, que llamaremos «build.xml»
En él, hay que definir la tarea de Ant para Hibernate Tools (hibernatetool). Además, hay que indicarle el classpath donde poder encontrar la clase
que implementa la tarea. También vamos a necesitar las librerías tanto de Hibernate como de Hibernate Annotations, por lo que las pondremos ya en el
classpath:
<project basedir="." > <property name="my.classpath" value="antClasspath"/> <path id="toolslib"> <path location="${my.classpath}/hibernate-tools.jar" /> <path location="antClasspath/hibernate-3.2.6.ga.jar" /> <path location="antClasspath/hibernate-commons-annotations-3.3.0.ga.jar"/> <path location="antClasspath/hibernate-annotations-3.3.0.ga.jar"/> </path> <taskdef name="hibernatetool" classname="org.hibernate.tool.ant.HibernateToolTask" classpathref="toolslib" /> </project>
Y ahora hay que meter la llamada a la tarea Ant propiamente dicha. La tarea precisa un sistema de trazas (commons-login)
y otras librerías que añadiremos al classpath (el resto de librerías necesarias las averiguamos por prueba/error; vemos lo que
dice que falta y «googleamos» 🙂
Otra cosa importante es que, cuando a la tarea le pasamos la ruta a las clases del proyecto, debemos pasarle la ruta a las clases compiladas
<project basedir="." > <property name="my.classpath" value="antClasspath"/> <property name="build.dir" value="antHbmGeneratedFiles"/> <path id="toolslib"> <path location="${my.classpath}/hibernate-tools.jar" /> <path location="${my.classpath}/hibernate-3.2.6.ga.jar" /> <path location="${my.classpath}/hibernate-commons-annotations-3.3.0.ga.jar"/> <path location="${my.classpath}/hibernate-annotations-3.3.0.ga.jar"/> <path location="${my.classpath}/commons-logging-1.1.1.jar"/> <path location="${my.classpath}/commons-collections-3.2.1.jar"/> <path location="${my.classpath}/dom4j-1.6.1.jar"/> <path location="${my.classpath}/persistence-api-1.0.jar"/> <path location="${my.classpath}/freemarker.jar" /> <path location="${my.classpath}/Tidy.jar"/> </path> <taskdef name="hibernatetool" classname="org.hibernate.tool.ant.HibernateToolTask" classpathref="toolslib" /> <target name="init"> <hibernatetool destdir="./${build.dir}"> <classpath> <path location="./target/classes" /> </classpath> <annotationconfiguration configurationfile="src/main/resources/hibernate.cfg.xml"/> <hbm2hbmxml/> </hibernatetool> </target> </project>
Os adjunto enlaces a las librerías:
- Librerías commons-*: página oficial de Apache Commons
- Librerías de Hibernate: página oficial de Hibernate. Commons Annotations
es una librería que viene en el Zip de hibernate-annotations - Dom4j: página oficial
- Freemarker: página de Freemarker en Source Forge
- Tidy: página de JTidy en Source Forge
- Persistence Api contiene el API de JPA, o sea que podéis encontrarlo en diversas fuentes
En cualquier caso, si utilizáis Maven en vuestros proyectos, la mayoría de estas librerías las podéis encontrar en los repositorios públicos.
Ejecutamos el Ant, y…
<?xml version="1.0"?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd"> <!-- Generated 16-may-2008 13:31:59 by Hibernate Tools 3.2.1.GA --> <hibernate-mapping> <class name="com.autentia.store.User" table="User"> <id name="id" type="java.lang.Integer" access="field"> <column name="id" /> <generator class="native"> </generator> </id> <property name="address" type="java.lang.String" access="field"> <column name="address" /> </property> <property name="creditCard" type="java.lang.String" access="field"> <column name="creditCard" /> </property> <property name="email" type="java.lang.String" access="field"> <column name="email" /> </property> <property name="name" type="java.lang.String" access="field"> <column name="name" /> </property> <property name="password" type="java.lang.String" access="field"> <column name="password" /> </property> </class> </hibernate-mapping>
Como vemos, los ficheros generados de ambas maneras son iguales, salvo por algún salto de línea. Ahora solo falta comprobar que los ficheros
son correctos… Para ello, vamos a elminar las anotaciones de las clases (aunque no las quitemos, debería hacer caso a los ficheros antes que
a las anotaciones, pero para estar seguros, las quitamos). Además de quitar las anotaciones, en el «hibernate.cfg.xml» cambiamos todas las entradas
de tipo «<mapping class=»…» />» por entradas del tipo «<mapping resource=»mificherogenerado.hbm.xml» />».
Probando los ficheros
Lo primero que vemos es que, si tenemos clases anotadas que hereden de otras, obtenemos un error
al intentar ejecutar nuestro proyecto… ya que, si bien en las subclases sí que genera el atributo
«discriminator-value», en la clase base no genera la etiqueta «<discrminator column=»» type=»»>».
Veamos un ejemplo.
<?xml version="1.0"?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd"> <!-- Generated 19-may-2008 9:32:08 by Hibernate Tools 3.2.1.GA --> <hibernate-mapping> <class name="com.autentia.store.product.Product" table="Product"> <id name="id" type="java.lang.Integer"> <column name="id" /> <generator class="native"> </generator> </id> <property name="description" type="java.lang.String"> <column name="description" /> </property> <property name="name" type="java.lang.String"> <column name="name" /> </property> <property name="price" type="float"> <column name="price" not-null="true" /> </property> </class> </hibernate-mapping>
Debería tener este otro aspecto:
<?xml version="1.0"?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd"> <!-- Generated 19-may-2008 9:32:08 by Hibernate Tools 3.2.1.GA --> <hibernate-mapping> <class name="com.autentia.store.product.Product" table="Product"> <id name="id" type="java.lang.Integer"> <column name="id" /> <generator class="native"> </generator> </id> <discriminator column="Type" type="string"/> <property name="description" type="java.lang.String"> <column name="description" /> </property> <property name="name" type="java.lang.String"> <column name="name" /> </property> <property name="price" type="float"> <column name="price" not-null="true" /> </property> </class> </hibernate-mapping>
Por otro lado, hay que tener en cuenta que nuestras clases tienen que tener los getters y setters para que se pueda acceder
a los campos de la entidad
Otro fallo que he encontrado al generar los ficheros de mapeo automáticamente es que no convierte las anotaciones «@cascade». Por ejemplo,
el siguiente fragmento de código
@OneToMany(cascade = CascadeType.ALL) private List<BuycartProduct> buycartProducts = new ArrayList<BuycartProduct>();
Lo traduce de la siguiente manera:
<!-- Todo el XML anterior --> <bag name="buycartProducts" inverse="false"> <key> <column name="Buycart_id" not-null="true" /> </key> <many-to-many entity-name="com.autentia.store.BuycartProduct"> <column name="buycartProducts_id" not-null="true" /> </many-to-many> </bag> <!-- Todo el XML posterior -->
Como podéis ver, la información sobre las acciones que deben ejecutarse en cascada se ha perdido… Esto causa problemas a la hora de
persistir el objeto si no se persiste también explícitamente la entidad asociada («BuycartProduct» en este caso).
Lo correcto habría sido
<!-- Todo el XML anterior --> <bag name="buycartProducts" inverse="false" cascade="all"> <key> <column name="Buycart_id" not-null="true" /> </key> <many-to-many entity-name="com.autentia.store.BuycartProduct"> <column name="buycartProducts_id" not-null="true" /> </many-to-many> </bag> <!-- Todo el XML posterior -->
Una vez que hemos tomado estas precauciones, empaquetamos nuestra aplicación y la desplegamos en nuestro
servidor de aplicaciones (en mi caso, un Tomcat 6.0.16) y…
Conclusiones
Como podéis ver, es fácil transformar nuestro entorno de Hibernate basado en clases anotadas para usar ficheros de configuración
XML, usando las Hibernate Tools… No obstante, si lo que queréis es migrar una aplicación con anotaciones a un entorno que utilice Java 1.4,
recordad que no sólo hay que quitar las anotaciones, sino que hay que eliminar también la utilización de Generics
(del tipo «List<String>»). Lo demás, como habéis visto, es sencillo…
Pero si a pesar de esta sencillez preferís que otros lo hagan por vosotros, os recuerdo que podéis contratar a
Autentia, expertos en el desarrollo de aplicaciones usando las tecnologías más avanzadas
(Spring, Hibernate, JSF, ICEFaces…)
Tengo una consulta… A ver si me podéis ayudar…
Estoy aprendiendo un poco y tengo un par que al ejecutarse se conecta a una bbdd creada, si está vacía crea las tablas, y sino las respeta…
Hasta ahí bien, pero tengo una tabla donde guardo estadísticas y para identificar al usuario tengo una foreing key que relaciona un número con el usuario… Pero al ir a borrar el usuario no me permite por la foreing key dear la tabla, si la borrow manualmente funciona correctamente.
Ahora en hibernate ¿Como debería definir el para conseguir eso? He estado probando y me gustaría que cuando se generarán las estadísticas se escribiese el valor correspondiente, y me permita borrar el usuario. Gracias Salu2!!