miércoles, marzo 18, 2009

Invocar procedimientos almacenados desde Hibernate (II)

Hace algún tiempo comenté una forma de invocar procedimientos almacenados (stored procedures) desde Hibernate.
Hoy os traigo otra forma menos manual (sin tener que manejar la connection). Básicamente consiste en definir la llamada al procedimiento almacenado en el fichero de mapeo de Hibernate. Personalmente los grandes ficheros XML no me gustan y considero las anotaciones un gran avance en ese sentido así que en próximos posts volveremos a retomar el tema haciendo uso de anotaciones.

Pero vayamos al grano. Lo primero será definir en el fichero que mapea la clase una llamada al procedimiento almacenado, en este ejemplo será: "p_get_members" al cual se le pueden pasar dos parámetros: "rank" y "from".

<sql-query name="getMembers" callable="true">
<return alias="m" class="com.abel.MembersDTO" mode="read">
<return-property name="id" column="ID/">
<return-property name="name" column="NAME/">
<return-property name="homeAddress">
<return-column name="HOME_STREET">
<return-column name="HOME_ZIPCODE">
<return-column name="HOME_CITY">
</return-property>
</return>
{call p_get_members (:rank, :from)}
</sql-query>
Como se puede ver se ha "envuelto" la llamada al procedimiento almacenado de forma que ahora desde java podremos invocar la query "getMembers" pasándole un par de parámetros y estaremos invocando el procedimiento almacenado. Además se han mapeado las columnas devueltas a los atributos del DTO: ID a id, NAME a name, y también he puesto en el ejemplo una compuesta, es decir, las columnas HOME_STREET, HOME_ZIPCODE y HOME_CITY se mapean al atributo homeAddress; ¡tened cuidado!, en este último caso el orden es muy importante.

Fijaros y no os olvidéis del parámetro callable=”true”. Sin ese parámetro no podréis invocar stored procedures.

Los tag <return-column> pueden ser omitidos si el alias devuelto coincide con el nombre del atributo en el que tiene que ser mapeado. A mi personalmente siempre me gusta declararlos ya que de esa forma se ve más claro cuando debes refactorizar o hacer cambios futuros (puede que el alias cambie en alguna refactorización).

Otro parámetro, en este caso opcional, es el lock-mode. Con esto mejoramos el rendimiento informando a Hibernate si el procedimiento es para sólo lectura (read) o de modificación (update).

Una vez hemos definido el procedimiento tendremos que invocarlo. Para ello haríamos algo similar a esto:
Session session = memberDAO.getSession();
Query query = session.getNamedQuery("getMembers");
query.setInteger("rank", 1);
query.setDate("from", initialDate);
List listOfManagers = query.list();
Por último pero no menos importante (last but not least que dirían los americanos) os aviso de que esta forma de invocar a los procedimientos almacenados tiene unas limitaciones que no tendríamos si lo hiciésemos directamente a través de la conexión (connection). Esto se debe a las diferencias entre las diversas bases de datos (la semántica y sintaxis):
  • No podréis usar las funciones setFirstResult() o setMaxResults() para paginar los resultados del stored procedure.
  • Se recomienda usar el estándar SQL92: {? = call functionName()} o {? = call procedureName(}. No está soportado las llamadas con sintaxis nativas.
Además, en función de la base de datos que estéis utilizando, consultar la documentación oficial de Hibernate ya que hay limitaciones propias de cada base de datos.

Y como ya sabeis, cualquier duda, corrección, opinión o comentario será bienvenido.

7 comentarios:

  1. ocupo llamar un procedimiento almacenado en Oracle desde Hibernate. El procedimiento no devuelve nada

    ResponderEliminar
  2. A no ser que hayan cambiado algo en estos dos últimos años ese código funcionaba perfectamente (ahora ya no trabajo con procedimientos almacenados)... repasa a ver si te has dejado algo...

    ResponderEliminar
  3. amm, me gustaria ver el tutorial que prometes con las Anotaciones, de cualquiermanera fue muy util, THX.

    chessmastersport@hotmail.com

    ResponderEliminar
  4. Vaya, tenía este post algo olvidado. Si no puse una tercera forma de hacerlo (usando anotaciones) fue porque cuando me puse a investigar me di cuenta que no era posible, pero de eso hace ya 2 años, quizá ahora haya alguna forma de mapearlos usando anotaciones.

    ResponderEliminar
  5. puedo trabajar con datePart y datediff en hibernate

    ResponderEliminar
  6. A mi me sale el siguiente error:
    Caused by: org.hibernate.MappingException: Named query not known: nombre_query

    ResponderEliminar