viernes, 7 de junio de 2013

Persistencia con JPA y Apache Tapestry

En Java disponemos de varias opciones para persistir la información a una base de datos relacional, algunas de ellas son:
Apache Tapestry
Hibernate

  • JDBC: es la API que viene integrada en la propia plataforma Java sin necesidad de ninguna librería adicional exceptuando el driver JDBC para acceder a la base de datos. Mediante esta opción se tiene total flexibilidad y evita la abstracción y sobrecarga de los sistemas como Hibernate y JPA. Se trabaja con el lenguaje SQL de forma directa y este lenguaje puede varia en algunos aspectos de una base de datos a otra con lo que para migrar a otra base de datos puede implicar reescribir las SQL de la aplicación. Su utilización de forma directa no es tan habitual aunque en casos que se necesite acceder de forma masiva a los datos puede se útil para evitar la sobrecarga o complejidad que añaden sistemas como Hibernate o JPA.
  • Hibernate: el modelo relacional de las bases de datos es distinto del modelo de objetos del los lenguajes orientados a objetos. Los sistemas ORM como Hibernate tratan de hacer converger el sistema relacional hacia un modelo más similar al modelo de objetos de lenguajes como Java, de forma que trabajar con ellos sea como trabajar con objetos. En ORM como Hibernate normalmente no se trabaja a nivel de SQL como con JDBC sino que se trabaja con objetos (POJO), las consultas devuelven objetos, las relaciones se acceden a través de propiedades y los borrados, actualizaciones y inserciones se realizan usando objetos y métodos. Los objetos POJO incluyen anotaciones que le indican a Hibernate cual es la información a persistir y las relaciones con otros POJO. Como hibernate dispone de esta información en base a ella puede recrear o actualizar las tablas y los campos necesarios según la definición de esas anotaciones. El ORM es encarga de traducir las acciones a las SQL entendidas por el sistema relacional, esto proporciona la ventaja adicional de que el ORM puede generar las sentencias SQL adaptadas a la base de datos utilizada. De esta forma se podría cambiar de una base de datos a otra sin realizar ningún cambio en la aplicación o con pocos cambios comparado con los necesarios usando JDBC. Con Hibernate se puede emplear un lenguaje de consulta similar a SQL pero adaptado al modelo orientado a objetos, el lenguaje es HQL.
  • JPA: es una especificación de Java que define una API común para los sistemas ORM. Con JPA podríamos cambiar de proveedor ORM sin realizar ningún cambio en la aplicación. JPA se ha basado en gran parte en Hibernate y su forma de trabajar es similar, el lenguaje HQL también es similar pero denominado JPQL.
A continuación explicaré como acceder a una base de datos relacional a través de JPA en una aplicación web usando el framework Apache Tapestry. Lo primero que deberemos hacer es crear el archivo persistence.xml donde indicaremos el driver JDBC según la base de datos que utilicemos y la URL de conexión a la base de datos entre otras opciones como el usuario y password de conexión. En el ejemplo he usado H2 como base de datos ya que puede embeberse en una aplicación sin necesidad de tener un sistema externo como ocurren en el caso de MySQL y PostgreSQL. De esta forma este ejemplo puede probarse sin necesidad de instalar previamente ninguna base de datos relacional.

En la aplicación es necesario incluir unas pocas dependencias para acceder a la API JPA.

Una vez incluidas las dependencias debemos configurar Tapestry para que nos proporcione el soporte de acceso a una base de datos empleando JPA, definimos en el módulo de la aplicación y contenedor de dependencias, el servicio DAO y al mismo tiempo configuraremos la transaccionalidad mediante anotaciones para el DAO.

Las clases con capacidad de persistencia han de ubicarse en un subpaquete del paquete de Tapestry. El paquete de Tapestry es aquel que está indicado en el parámetro de contexto tapestry.app-package en el archivo web.xml de la aplicación web. Si tapestry.app-package fuese es.com.blogspot.elblogdepicodev.tapestry.jpa el paquete de las entidades debería ser es.com.blogspot.elblogdepicodev.tapestry.jpa.entities. Esta es la convención y la forma preferida de hacerlo, si se quiere cambiar es posible hacerlo mediante configuración. La entidad de persistencia que usaré para el ejemplo es la siguiente, en la que se incluyen las anotaciones de JPA:

El código de acceso a base de datos suele ponerse en una clase denominada servicio y DAO (Data Access Object) que contiene todo ese código. Ya que las operaciones de acceso a base de datos son candidatas a ser reutilizadas desde varias páginas o componentes es recomendable hacerlo así, además de hacer que las páginas de Tapestry sean más pequeñas (ya tienen suficiente responsabilidad con hacer de controlador en el modelo MVC) permite que si un día cambiásemos de framework web solo tendríamos que modificar la capa de presentación. Todo el código de los servicios nos serviría perfectamente sin hacer ninguna modificación.

El contenedor de dependencias se encargará en el momento que necesite construir una instancia del servicio DAO inyectarle en el constructor los parámetros necesarios. En este caso una de las clases principales de la API JPA que es EntityManager. Una vez con la referencia a entityManager trabajaríamos usando sus métodos para realizar las consultas y operaciones que necesite proporcionar el DAO.

Hay métodos del DAO que finalmente lanzan sentencias de inserción, modificación y borrado, este DAO es muy sencillo pero si realizase varias operaciones contra la base de datos estas deberían cumplir las reglas ACID. Para cumplir las reglas ACID hay que utilizar transacciones, se puede conseguir de forma declarativa utilizando la anotación @CommitAfter. Esta anotación inicia una transacción al iniciarse el método y lanza un commit al finalizar el método. En caso de que se produzca una excepción se seguirá haciendo commit para las excepciones checked (de obligado tratamiento o lanzado) y únicamente un rollback para las excepciones unckecked (que extienden de RuntimeException y no es necesario capturarlas o lanzarlas).

Si quisiésemos hacer rollback en una excepción checked deberíamos hacerlo manualmente capturando la excepción y lanzando una unchecked aunque si es la opción que queremos por defecto podríamos implementar una anotación para tratar la transaccionalidad de esta manera y de forma declarativa en todos los casos.

Una vez que tenemos el servicio DAO basta con inyectarlo en la clase de una página o componente y hacer uso de él. En la siguiente página se muestra un listado de productos donde con un botón se pueden eliminar y con otro se pueden crear nuevos. En la clase de la página se hace uso del DAO para buscar las entidades, crear una nueva con un nombre y descripción aleatoria y eliminar un producto.

Esta es una captura de pantalla del ejemplo en ejecución:
Para casos más complejos de transacciones como transacciones anidadas, requeridas, soportables, ninguna u obligatoria podemos basarnos en lo comentado en esta entrada donde queda perfectamente explicado.

Como en el resto de entradas el código fuente completo lo puedes encontrar en mi repositorio de GitHub. Si quieres probarlo en tu equipo lo puedes hacer de forma muy sencilla con los siguientes comandos y sin instalar nada previamente (salvo java y git). Si no dispones de git para clonar mi repositorio de GitHub puedes obtener el código fuente del repositorio en un archivo zip con el anterior enlace.

Referencia:
DAO genérico para JPA (ORM)
Documentación sobre Apache Tapestry
http://es.wikipedia.org/wiki/Java_Database_Connectivity
https://en.wikipedia.org/wiki/Relational_database
http://en.wikipedia.org/wiki/Cardinality_(data_modeling) http://en.wikipedia.org/wiki/ACIDhttp://en.wikipedia.org/wiki/SQL http://en.wikipedia.org/wiki/NoSQLhttp://en.wikipedia.org/wiki/Hibernate_(Java)