Aug 13

Configurando Hibernate para test unitarios.

Cuando hacemos test de JUnit o la herramienta que sea, una de las cosas difíciles de probar son las transacciones con base de datos. En nuestro entorno de desarrollo necesitaríamos una base de datos igual que la de producción y deberíamos borrar todos los datos, meter los datos previos al test para tener la base de datos en un estado conocido, hacer el test y luego revisar el contenido de la base de datos. Esto, aparte de complejo, puede ser muy lento con una conexión a una base de datos real.

Una de las soluciones es utilizar una base de datos en memoria (como HSQLDB, H2, etc). Estas bases de datos suelen ser un jar que no necesita instalación, así que basta con añadirlo a nuestro classpath en los test. Al ser en memoria, los test serán también muy rápidos. Sin embargo, sigue habiendo dos problemas:

  • En cada test habría que crear todas las tablas desde cero.
  • Las SQL de estas bases de datos pueden no ser las mismas que la de nuestra base de datos de producción (MySQL, Oracle, etc).

Para ayudarnos a solucionar estos problemas podemos usar Hibernate (u otra herramienta similar). Si en el fichero de configuración de Hibernate, los de mapeo de clases (o anotaciones) y nuestro código no usamos nada específico de una base de datos (debería ser lo normal salvo casos excepcionales), Hibernate nos hará independientes de la base de datos que usemos (de hecho, ese es uno de sus objetivos). Por otro lado, hibernate tiene un parámetro de configuración hibernate.hbm2ddl.auto en el que le podemos indicar que cree desde cero todas las tablas de la base de datos. Así que Hibernate nos resuelve, en principio, los dos problemas mencionados.

La primera idea que se nos ocurre es tener dos ficheros de configuración de Hibernate (hibernate.cfg.xml), uno para producción con la base de datos real y otro para test. Pero somos buenos programadores y no nos gusta tener cosas repetidas (principio DRY), posiblemente esos ficheros sean prácticamente iguales salvo los parámetros de conexión a la base de datos y el mencionado parámetro hibernate.hbm2ddl.auto, que en nuestro base de datos de test valdrá "create" y en la de producción puede valer algo como "verify".

La solución es simple, basta tener un único fichero de configuración para la base de datos de producción, con toda la parámetrica real. Para los test cargamos ese fichero … y modificamos en código sólo lo que nos interese. Se puede hacer de esta forma

Configuration configuration = new Configuration().configure();
configuration.setProperty("hibernate.connection.url", "jdbc:h2:mem:databaseName");

configuration.setProperty("hibernate.hbm2ddl.auto","create");
SessionFactory sessionFactory = configuration.buildSessionFactory();
 

El método configure() de Configuration leerá el fichero por defecto de configuración (suponemos que es el de producción) y luego, con el método setProperty() podemos "machacar" todas las propiedades que queramos de ese fichero. En el ejemplo sólo hemos cambiado la url de la base de datos y hibernate.hbm2dll.auto, aunque posiblemente deberíamos cambiar también el nombre de la clase del Driver de base de datos, usuario, password, etc.

Ahora sólo nos quedaría poner este código en algún sitio al que nuestras clases de test puedan acceder. Nuestras clases de test obtendrían el sessionFactory usando este código, mientras que el código de producción lo obtendría de la forma habitual, posiblemente a través de un UtilHibernate.java.

Por supuesto, no nos libra nadie de llenar la base de datos en cada test con unos datos conocidos y analizar luego los resultados.

Nos queda un detalle, que es borrar los datos después de cada test. Utilizando la base de datos en memoria como hemos hecho en el código anterior, quedará "viva" y con datos mientras esté arrancada la máquina virtual java. Al ejecutar una batería de test (con mvn test, por ejemplo), la máquina virtual no muere hasta que acaban todos los test, por lo que los datos que unos test van dejando en base de datos quedan para los siguientes test, aunque estén en memoria. Debemos vaciar esos datos después de cada test.

Una opción "pesada" es ir borrando los datos o hacer un "drop" de las tablas, en el orden adecuado para evitar problemas de "contraints". Pero otro método rápido y sencillo consiste en simplemente cerrar el SessionFactory. El siguiente test tendrá que crear un SessionFactory nuevo (con el código de arriba) y así se volverán a crear todas las tablas desde cero. Es decir, nuestras clases de test tendrán los métodos setUp() y tearDown() (o @Before y @After en JUnit 4) de la siguiente forma

public class TestUno extends TestCase {
   private SessionFactory = sessionFactory;

   @Override
   public void setUp() {
      sessionFactory = // Obtener session factory con el codigo anterior
   }

   @Override
   public void tearDown() {
      sessionFactory.close();
   }
   …
}

 

Simplemente un detalle. De esta manera realmente no se borran los datos al terminar en test. En realidad se borran al empezar el siguiente test, ya que el "create" de hibernate.hbm2dll.auto hace exactamente eso, borrar para crear desde cero.

 

Jul 08

Hibernate buildSessionFactory() deprecated

Jugando con Hibernate, me he puesto con la última versión, la 4 no sé cuántos. Me cojo mi viejo código, copy-paste para empezar a hacer algo y me encuentro con 

return new Configuration().configure().buildSessionFactory();

Pues sí, el método buildSessionFactory() está obsoleto. Mirando la API, te dice que uses en su lugar buildSessionFactory(ServiceRegistry). Chachi piruli, ¿y qué demonios es y de dónde saco un ServiceRegistry de esos? Ale, toca mirar la documentación oficial y nos encontramos esto http://docs.jboss.org/hibernate/orm/4.1/quickstart/en-US/html_single/#hibernate-gsg-tutorial-basic-test donde usan, por supuesto, el método obsoleto.

A googlear un poco y veo que es un problema compartido por mucha gente, nadie encuentra una solución oficial para ello. En Stack Overflow muestran una solución que funciona y que es más o menos la que ha empezado a seguir la gente Así que nada, copy-paste de aquí y allá y aquí queda 

Configuration configuration = new Configuration().configure();

ServiceRegistry serviceRegistry = new ServiceRegistryBuilder().applySettings(configuration.getProperties())
              .buildServiceRegistry();
SessionFactory sessionFactory = configuration.buildSessionFactory(serviceRegistry);

 

Este lee el fichero de configuración por defecto de hibernate, el hibernate.cfg.xml en el classpath.

 

Apr 30

Hibernate: Clases persistentes con el mismo nombre

En mis jueguecitos con Hibernate, me he tropezado con otra tontería posiblemente evidente para el que sabe un poco de Hibernate.

Hibernate, tanto si usa ficheros de mapeo hbm.xml como si usa anotaciones, tiene en cuenta el paquete de las clases para saber dónde localizarlas. En principio no hay problemas si tenemos dos clases con el mismo nombre en paquetes distintos, por ejemplo, paquete1.Persona y paquete2.Persona.

Pero sí hay un problema que hace que hibernate proteste en el arranque si tenemos dos clases persistentes con el mismo nombre y en paquetes distintos. En el lenguaje de consulta HQL (Hibernate Query Language) por defecto se usa el nombre de la clase para saber qué estamos consultando y en este caso hay dos Persona. Con un session.createQuery("from Persona") hibernate no sabe qué clase es la que queremos.  El error que sale es algo como

org.hibernate.AnnotationException: Use of the same entity name twice: Persona

org.hibernate.DuplicateMappingException: duplicate import: Persona refers to both paquete1.Persona and paquete2.Persona

Por fortuna Hibernate lo tiene contemplado. Tenemos que dar al menos a una de estas clases otro nombre de entidad para hibernate. No tenemos que cambiar el nombre de la clase, sino sólo avisar a Hibernate para que la contemple con otro nombre. Esto se hace tal que así

// Con anotaciones
@Entity(name="OtraPersona")
public class Persona {

<!– En el hbm.xml –>
<class name="paquete2.Persona" entity-name="OtraPersona" …

 

Y tenemos que tenerlo también en cuenta a la hora de hacer las asociaciones con otras tablas. Yo he probado a dejar el class="paquete1.Persona" y me ha fallado igualmente.

<one-to-many entity-name="OtraPersona" />

A la hora de trabajar con nuestro código java, usaremos

session.createQuery("from Persona")
session.createQuery("from OtraPersona")

No lo he probado, pero según la documentación esto de dar un nombre de entidad distinto podría hacerse con una única clase. Por ejemplo, si sólo tenemos paquete.Persona, podemos mapear dos veces esta clase y darle dos nombres de entidad distintos, por ejemplo, PersonaGuay y PersonaCutre. De esta forma, la misma clase Persona podríamos guardarla en una tabla PERSONA_GUAY o en la tabla PERSONA_CUTRE, decidiendo en tiempo de ejecución dónde debe ir. Supongo que este es el motivo de que en la asociación haya que poner el entity-name en vez de el class.

 

Apr 26

Algo de Hibernate básico: get() y load()

 Sigo jugando/leyendo Hibernate y algo básico que me ha llamado un poco la atención. En Hibernate para obtener un objeto de la base de datos por medio de su clave primaria tenemos dos métodos: get() y load()

get() se añadió posteriormente a load() y su uso es muy tonto. Le pasamos el id y carga el objeto de base de datos. Devuelve null si no lo encuentra.

load() es anterior y tiene un comportamiento un tanto peculiar. Si no encuentra el objeto en base de datos, salta una excepción. Si lo encuentra, no lo carga, sino que te devuelve un proxy del objeto. La carga real se hace cuando intentamos obtener datos de ese proxy, por lo que incluso pueden saltarnos excepciones después, cuando estemos usando los métodos get del proxy, ya que es cuando se intentará realmente la carga del objeto de la base de datos.

¿Cuando usar uno u otro?. A criterio del consumidor, pero una posibilidad (y es sólo una idea) es usar get() cuando la no existencia de un objeto en base de datos con un id determinado pueda ser una cosa normal, por ejemplo, una base de datos de empleados de una empresa y usamos el DNI como clave primaria. Un usuario de nuestra aplicación introduce un DNI y se equivoca al hacerlo o simplemente pone un DNI de alguien que no es de la empresa.

Se usaría load() cuando la no existencia de un objeto con un id determinado puede considerarse un error, por ejemplo, usar como id una columna específica de la base de datos que no tiene ningún sentido en nuestro modelo de datos. Ese id se ha tenido que obtener de base de datos y si ya no existe, es porque alguien ha borrado o se ha sacado el id de la manga. O no existe el id del usuario de administración de nuestra aplicación, o el número de versión de la aplicación, o cualquier otro dato que nuestra aplicación requiera inexcusablemente en base de datos.

 

Apr 25

Jugando con Web Services e Hibernate. Método setter de las Collection.

 En un rato de entretenimiento me he dedicado a generar las clases java a partir de unos WSDL y XSD de Web Service. Para ello he usado wsdl2java de Apache CXF. Para que el entretenimiento fuera mayor, decidí hacer persistentes estas clases generadas usando Hibernate. Como no eran muchas clases y quiero aprender un poco de Hibernate, mi gran asignatura pendiente, me decido a hacer los ficheros hbm.xml a mano.

Me pongo a ello, hago mis primeras pruebas y tras corregir los errores evidentes, llega el que me ha llamado la atención y es motivo de este post.

Caused by: org.hibernate.PropertyNotFoundException: Could not find a setter for property poligonPositions in class

 

El error parece claro, en la propiedad PoligonPositions (un List dentro de la clase AreaGeografica), Hibernate no puede encontrar un método set de acceso a dicha propiedad, es decir, un setPoligonPositions(List). Miro la clase en cuestión y efectivamente, no hay método set. ¡Qué raro!, parece que el wsdl2java no ha generado el método set.

Venga a mirar la herramienta wsdl2java a ver si tiene alguna configuración o algo y no, no la he encontrado. Rebuscando en internet me encuentro que la especificación jax-ws dice que las colecciones no deben tener método set(). La clase generada, en el atributo, tiene añadido este comentario

/**
* Gets the value of the poligonPositions property.
*
* <p>
* This accessor method returns a reference to the live list,
* not a snapshot. Therefore any modification you make to the
* returned list will be present inside the JAXB object.
* This is why there is not a <CODE>set</CODE> method for the poligonPositions property.
*
* <p>
* For example, to add a new item, do as follows:
* <pre>
* getPoligonPositions().add(newItem);
* </pre>
*
*
* <p>
* Objects of the following type(s) are allowed in the list
* {@link Position }
*
*
*/
public List<Position> getPoligonPositions() {
 

 

que básicamente quiere decir que no se pone el set y que debemos llamar a getPoligonPositions().add(newItem).

Bueno, parece que por el lado de los Web Services no hay solución a esto, poner el set() a mano tampoco parece adecuado en una clase que ha sido generada automáticamente y que se puede "machacar" en cualquier momento.

Afortunadamente, en la parte de Hibernate si tenemos solución. En el fichero hbm.xml de la clase se puede poner algo como

<hiberante-mapping>
   <class …..>
      ….
     <list name="poligonPositions" … access="field">
         …
    </list>
     …
   </class>
</hibernate-mapping>

El atributo access="field" hace que hiberante, en vez de usar el método set(), vaya directamente al atributo.

 

Feb 12

Hibernate: Annotations vs XML

En varias ocasiones he visto sobre el tema de persistencia en java la discusión de si es mejor usar annotations o ficheros de configuración XML.

Mi primera opinión, sin pensar mucho más, es que era mejor los ficheros de configuración XML, sin lugar a dudas. ¿Por qué?. Pienso que los bean de java deben ser reutlizables de proyecto en proyecto, que la base de datos es un tema aparte cuyas tablas pueden cambiar de un proyecto a otro y no me gusta meter dependencias de jar raros en mis clases básicas del modelo de datos. Las annotations de persistencia necesitan esos jar para compilar. De alguna forma, pienso que estoy "casando" mis clase con una herramienta específica.

Sin embargo, ahora que me he metido a jugar un poco con hibernate más en serio y empiezo a comprender la filosofía de trabajo de estas herramientas de persistencia, empiezo a dudar si es mejor los ficheros XML.

Por un lado, estoy mal acostumbrado en los proyectos en los que trabajo. Alguien diseña la base de datos primero, o incluso nos la da el cliente ya hecha y tenemos que hacer código para tratar con esa base de datos. De proyecto en proyecto, aunque la temática es la misma, nos hacen cambios en las bases de datos. Por ello, nuestras aplicaciones pretenden tener su propio modelo de datos a base de java beans y tratan de aislarse lo más posible de los detalles de la base de datos. El patrón DAO se nos hace fundamental.

Pero veo que la forma de trabajo de Hibernate está pensada al revés. Tú te haces tu modelo de datos con java beans y te olvidas de la base de datos. Con annotations o con ficheros XML haces el mapeo de esos beans sobre la base de datos e Hibernate se encarga de crear todas las tablas. Y pensándolo de esta forma, en que lo principal son tus java beans y las tablas de base de datos son "secundarias", empiezo a ver ventajas en las annotations sobre los ficheos XML.

Si se hacen java beans y ficheros XML de mapeo separados, estamos hasta cierto punto violando el principio DRY (Don`t repeat yourself). Si tocamos los java beans, debemos acordarnos de tocar el XML correspondiente y al revés. Debemos tocar en dos sitios distintos si cambia el tipo de una columna, añadimos o borramos un atributo a un java bean, etc. Con annotations es más dificil el despiste. Si cambio un atributo, lo añado o lo borro, la annotation está justo al lado y es más difícil olvidarse de ella.

Además, si vamos a reutilizar nuestros beans en otros proyectos y vamos a seguir el mismo mecanismo de persistencia, dejando a Hibernate o la herramienta de turno crear las tablas de base de datos, no es tan malo llevarse los bean con sus annotations. De hecho, es más cómodo que llevarse los bean y además sus ficheros xml.

La única pega que le veo a esto es que las annotations en ocasiones se complican demasiado, quitando claridad a un código en principio simple (un java bean). Y como ejemplo, este trozo de código sacado de la documentación de Hibernate, donde sólo se empieza a declarar la clase Forest (si, fíjate al final, la última línea….)

@Entity
@BatchSize(size=5)
@org.hibernate.annotations.Entity(
        selectBeforeUpdate = true,
        dynamicInsert = true, dynamicUpdate = true,
        optimisticLock = OptimisticLockType.ALL,
        polymorphism = PolymorphismType.EXPLICIT)
@Where(clause="1=1")
@org.hibernate.annotations.Table(name="Forest", indexes = { @Index(name="idx", columnNames = { "name", "length" } ) } )
@Persister(impl=MyEntityPersister.class)
public class Forest { ... }

 

Feb 10

Otra vez Hibernate

 

Hace algún tiempo me puse a jugar con Hibernate y no me causó demasiada buena impresión. Me daba muchos problemas, las herramientas que bajaba parecian bastante descuidadas, no me funcionaba nada bien sin tener que pelearme con ello y un sin fin de descalabros. Pero estos días atrás me entró remordimiento de conciencia. De aquella, había empezado supongo que de mala manera, sin saber nada de hibernate, tratando de arrancar las herramientas de "ingeniería inversa", es decir, tratar de sacar de las tablas de base de datos ya creadas los bean de java y los xml de mapeo. Así que hace unos días me decidí a retomar el tema desde cero, empezando con la documentación de Hibernate y siguiéndola más o menos paso a paso.

Pues creo que me reafirmo en mi idea. La documentación, aunque están claros los conceptos que explica, deja bastante que desear en cuanto a cómo arrancar el ejemplo. Ahi van algunas pegas:

  • Si te bajas hibernate, no vienen ahí todos los jar necesarios. Aparte del conector con la base de datos, se echa en falta una implementación para slf4j-api. Hibernate trae la Api, pero no la implementación y no funciona el ejemplo sin una implementación. Sí, la documentación dice que es necesaria una implementación en el punto 3.5,  pero el ejemplo y el arranque del mismo está en el punto 1.2. No parece el orden adecuado eso de "tú arranca, que más adelante te diré qué está mal".
  • Si intentas la configuración con maven, en la documentación no pone el groupId a usar, sino una variable ${groupId} que no está definida en ningún sitio. Bueno, no cuesta mucho inventarse un valor y sacarlo, pero podrían haberlo puesto. Por cierto, tampoco pone la versión.
  • Si sigues con maven, no se baja el jar de javassist, sea lo que sea eso. El caso es que en el pom.xml debes poner la dependencia a mano y en la documentación no lo indica.
  • Además, por supuesto, falta la dependencia de la implementación de slf4j-log4j (o el logger que quieras). La podemos poner sin problemas, pero hay que tener cuidado, porque la versión que se ponga de dicha implementación debe coincidir con la versión de slf4j-api de la que depende hibernate y, que por supuesto, tampoco se ve directamente en ningún sitio.

En fin, que el ejemplo muy bien, pero ponerlo en marcha cuesta unos cuantos ensayo, error y googlear, añadiendo dependencias y jars, según nos van saliendo errores. No me extrañan nada comentarios como este de Nicolás.

Así que como siempre, copiando de la documentación y ampliándola precisamente en estas pequeñas pegas, ahí va mi "Hola mundo con hibernate", en el que espero quede más claro exactamente que jars necesitamos y cómo arrancar la aplicación de la documentación.

Y de paso, sigo leyendo la documentación dichosa, a ver si aprendo algo de Hibernate. Lo que he leido y probado hasta ahora me está gustando bastante.