lunes, diciembre 22, 2008

Invocar desde Hibernate procedimientos almacenados en Oracle

En la aplicación que estoy desarrollando la persistencia se realiza con Hibernate. En general nosotros nos lo guisamos todo pero para algunas funcionalidades complejas y que requieren "comunicarse" con otra aplicación ya desarrollada me dicen el nombre del procedimiento almacenado (stored procedure) que ya existe y los parámetros que le debo pasar (en la mayoría de los casos Arrays) de forma que yo no tengo que implementar esa lógica. Cada vez que tengo alguna duda con Hibernate suelo consultar el libro Java Persistence by Hibernate, que me ha resuelto más de una y dos dudas pero en este caso no me sirvió de nada. No me decía como llamar a un PL/SQL mediante la "current session" de Hibernate, para los que tengais el mismo caso os muestro como lo he resuelto yo (quizá haya mejores formas... pero esta garantizo que funciona).

Algunos de las clases que se van a usar:

import oracle.jdbc.OracleConnection;
import oracle.jdbc.OracleTypes;
import oracle.jdbc.OracleCallableStatement;
import oracle.sql.ARRAY;
import oracle.sql.ArrayDescriptor;
Y a continuación un ejemplo de como invocariamos el procedimiento "p_calcular" que está dentro del package "pack". Este Stored Procedure tiene cuatro parámetros de entrada y devuelve un Number:

OracleCallableStatement callable = null;
try {
// conseguimos la conexion con la que llamaremos al procedimiento
OracleConnection conn = (OracleConnection) getSession().connection().getMetaData().getConnection();
// establecemos el paquete al que vamos a llamar
callable = (OracleCallableStatement) conn.prepareCall("{call pack.p_calcular (?, ?, ?, ?)}");

// declaramos el parámetro que nos devolverá el procedimiento
callable.registerOutParameter(1, OracleTypes.ARRAY, "T_REF"); // NUMERIC

// en esta variable enviaremos los parámetros de entrada
ARRAY newArray;

// declaramos los tipos de los parámetros de entrada, estos tipos tendrán que estar declarados en el Oracle
ArrayDescriptor tRef = ArrayDescriptor.createDescriptor("T_REF", conn);
ArrayDescriptor tMatricul = ArrayDescriptor.createDescriptor("T_MATRICUL", conn);

// rellenamos los parámetros de entrada con los valores que queremos pasar
Long[] param1 = new Long[] { unitId };
newArray = new ARRAY(tRef, conn, param1);
callable.setArray(1, newArray);

String[] param2 = new String[] { memberId };
newArray = new ARRAY(tMatricul, conn, param2);
callable.setArray(2, newArray);

Long[] param3 = new Long[] { Long.valueOf(userId) };
newArray = new ARRAY(tRef, conn, param3);
callable.setArray(3, newArray);

Integer[] param4 = new Integer[] { planning.getPriority() };
newArray = new ARRAY(tRef, conn, param4);
callable.setArray(4, newArray);

// invocamos al procedimiento
callable.execute();

// obtenemos el array con los resultados devueltos
ARRAY array = callable.getARRAY(1);
Integer newPlanningId = 0;
int[] result = array.getIntArray();

if (result.length > 0) {
// en este ejemplo el procedimiento sólo devuelve un valor entero
newPlanningId = result[0];
}
return newPlanningId;
} catch (SQLException sqle) {
logger.error(sqle.toString());
throw new Exception(sqle);
} finally {
try {
callable.close();
} catch (Exception e) {
logger.error(e.getMessage());
}
}
Si conoceis otra forma más sencilla de hacer esto, por favor, ponérmelo en los comentarios.

Actualizado 18/03/2009: Aquí encontrareis otro forma de hacerlo. Es más automatizada pero con algunas limitaciones. Echadle un ojo antes de decidiros por una u otra forma.

4 comentarios:

  1. Puta madre cuantas chingaderas y todo
    para ganarse la vida........

    Vale verga..............

    Pinches riquellos de mierda, si fuera uno
    de ellos estudiaria mi ciencia favorita Fisica-matematica y saldria de este mundo enfermo de la programacion...

    ResponderEliminar
  2. Ufff, no he entendido nada de tu comentario...

    ResponderEliminar
  3. yo he probado hacerlo con ibatis o hibernate, y me ha funcionado siempre y cuando los tipos de datos de los arreglos declarados en oracle esten en el esquema, no dentro de un paquete, osea que por ejemplo "T_REF" este declaro solo dentro del esquema, no se si se podra que "T_REF" este dentro de un paquete..?

    ResponderEliminar
  4. Sip, los tipos tienen que estar declarados a nivel de esquema. Si descubres como hacerlo con los tipos de dentro del esquema ya me dirás.

    ResponderEliminar