Mapeo de Procedimientos Almacenados con Hibernate

Mapeo de Procedimientos Almacenados con Hibernate

 

Índice de contenidos.

1. Introducción

En este tutorial vamos a ver la forma de trabajar con Hibernate para que podamos llamar a los procedimientos almacenados de nuestra base de datos. En muchos desarrollos es común ver como tienen toda o parte de la lógica de negocio en la base de datos. En estos casos, debido a que no vamos a replicar esta lógica de negocio en el código fuente de la aplicación para utilizarlo desde la capa de negocio, podemos invocarlo mediante queries nativas y el resultado de las mismas a entidades para que sea más sencillo trabajar con los datos.

La forma de mapear las queries nativas está disponible tanto vía anotaciones como mediante el fichero de mapeo hbm.xml. En este tutorial usaremos anotaciones. Más info aquí.

Para la base de datos utilizaré PostgreSQL.

Para hacer las pruebas me basaré en el código fuente del siguiente tutorial.

El código fuente del ejemplo lo puedes descargar de aquí.

2. Entorno

  • MacBook Pro 15′ (2.4 GHz Intel Core i5, 4GB DDR3 SDRAM).
  • Sistema Operativo: Mac OS X Snow Leopard 10.6.4
  • JDK 1.6.0_20
  • Hibernate Core 3.3.2.GA
  • PostgreSQL 9.0.2
  • JUnit 4.7

3. Procedimiento Almacenado

Lo primero que haremos será crearnos un procedimiento almacenado en PostgreSQL para posteriormente mapearlo con Hibernate. La lógica del procedimiento es lo de menos, en este caso el procedimiento findByNumber se encarga de devolver un registro con los datos del futbolista al cual corresponde el número recibido por parámetro.

4. Mapeo de Hibernate

Una vez que tenemos el procedimiento almacenado en la base de datos vamos a mapearlo a través de una query nativa. Las queries nativas se utilizan para escribir en sql estándar (en lugar de HQL) una consulta que pedimos a Hibernate que lance. Mediante esta query nativa llamamos al procedimiento almacenado y su resultado se guardará en la entidad Footballer.

A través de la anotación @NamedNativeQuery indicamos la query nativa llamada Footballer.findByNumber. La query se especifica mediante el atributo query donde hacemos la llamada al procedimiento almacenado que definimos anteriormente. Debido a que lo que nos devuelve la base de datos es un registro debemos sacar los datos para posteriormente ser mapeados a través del ‘select … as’ indicando su nombre y tipo como se muestra en la consulta anterior. Por último, a través del parámetro resultClass indicamos la clase donde mapear los datos, en este caso la clase Footballer. No es necesario indicar los atributos de la clase si se llaman igual que los que devuelve la consulta que llama al procedimiento. Si fueran diferentes se lo debemos indicar a través de un resultSetMapping mapendo tanto la entidad destino de los datos como sus atributos. Este sería el ejemplo de ese caso:

De esta manera le decimos a Hibernate cómo debe mapear los datos en los atributos de la entidad. Esta forma es menos recomendable que la primera ya que implica mayor configuración, recordar el patrón Convention over Configuration. Si decidimos utilizarla debemos indicar el resultSetMapping que indicará la forma de mapear los datos devueltos por la consulta. Para ello definimos un “mapping” mediante la anotación SqlResultSetMapping donde configuramos la entidad destino de los datos “Footballer.class” y los campos dentro de la entidad donde recoger los datos de la query a través de los FieldResult.

5. Llamada a la NamedNativeQuery

Por último ya únicamente falta la llamada al procedimiento almacenado, para ello nos creamos un método en nuestro Dao llamado “findByNamedQuery”. Este método recibe el nombre de la namedQuery, parámetros para indicar el primer registro a sacar en la consulta y el máximo de registros y por último los filtros de la consulta.

Para comprobar que todo está bien lo probamos mediante un test de JUnit. Antes de ejecutarse el test Spring realizará la llamada al método init del bean AddSampleData que proveerá de datos a la base de datos.

El test de JUnit busca un futbolista por su número de camiseta llamando al procedimiento almacenado “Footballer.findByNumber” y comprueba que se recibe en el listado un futbolista que cumple los criterios de la búsqueda. Se le pasa al método findByNamedQuery los valores firstResult = -1 y maxResult= -1 ya que no son relevantes en esta consulta.

Para completar la prueba de concepto vamos a crear otro procedimiento almacenado que se encargará de sumar dos números y devolver el resultado de la suma. Lógicamente la funcionalidad del procedimiento es lo de menos, lo que nos interesa en este caso es el resultado de una operación que no formará parte de una entidad sino que será un escalar por lo que los mapeos son diferentes.

Lo primero será crear el procedimiento almacenado encargado de realizar la suma:

Creamos una nueva NamedNativeQuery en la entidad Footballer. Se mete en esta entidad por simplificar el ejemplo aunque no tenga nada que ver 😉

Mediante la query nativa “Footballer.SumaDeEnteros” llamamos al procedimiento almacenado “suma” que recibe dos enteros y devuelve el resultado. Este resultado será un valor escalar y la forma de indicarle a Hibernate que trate este valor es mediante el SqlResultSetMapping donde se define el nombre que llevará el dato en el ResultSet de la consulta.

Para probar esta nueva funcionalidad añadimos un nuevo test de JUnit donde realizamos la llamada a la NativeNamedQuery pasándole los valores 2 y 3 y comprobamos el valor de la suma.

6.Conclusiones

Muchas veces hemos escuchado que una aplicación o módulo funcional no se puede migrar a Hibernate (o Java) debido a que hay mucha lógica de negocio en procedimientos almacenados de la base de datos. Como hemos podido ver no es excusa tener estos procedimientos ya que Hibernate es capaz de llamar a esta lógica y mapear el resultado por lo que nos podemos beneficiar de las grandes ventajas que nos proporciona Hibernate para trabajar con el modelo de datos.

Espero que te haya servido de ayuda.

Un saludo. Juan.