Software Guru Conference & Expo 2014

Aportación JavaMéxico - PCJ Ejecutar un procedimiento almacenado (PL/SQL) desde Java

Hola, para todos aquellos que tienen procedimientos almacenados en sus bases de datos y necesiten ejecutarlo desde una aplicacion Java aqui le muestro un ejemplo:

Primeramente debemos crearnos un metodo que concatene el paquete del procedimiento, el nombre del procedimiento y sus respectivos parametros, este metodo puede ir en su clase de Utilerias:

public class Utils
{

public static String llamarProcedimiento(String nombrePaquete, String nombreProcedimiento, int numeroParametros) {
        StringBuffer sb = new StringBuffer("{call " + nombrePaquete + "." + nombreProcedimiento + "(");
        for (int n = 1; n <= numeroParametros; n++) {
            sb.append("?");
            if (n < numeroParametros) {
                sb.append(",");
            }
        }
        return sb.append(")}").toString();
    }
}

Despues en su clase de constantes puede declarar un atributo que mande a llamar al metodo anteriormente creado:

public class Constantes
{
      public static String PROCEDIMIENTO = Utils.llamarProcedimiento("nombre_del_paquete", "nombre_procedimiento_almacenado", 3);
}

Y finalmente en su clase DAO mandar a llamar al atributo PROCEDIMIENTO de la clase Constantes:

public void ejecutarProcedimiento(int parametro1, int parametro2, String parametro3) throws Exception {
        Connection con = null;
        CallableStatement cs = null;
        try {

            con = ... //Obtenemos la conexion a la Bade de Datos

            cs = con.prepareCall(Constantes.PROCEDIMIENTO); // Mandamos a llamar al atributo PROCEDIMIENTO de nuestra clase de Constantes

            // Cargamos los parametros de entrada
            cs.setInt(1, parametro1);
            cs.setInt(2, parametro2);
            cs.setString(3, parametro3);

            // Ejecutamos
            cs.execute();

        } catch (Exception e) {
            throw new Exception(e); //Propagamos la Excepcion
        } finally {
            if(cs != null) //Finalizamos cerrando el CallableStatement
                cs.close();

            if(con != null) //Finalizamos cerrando la conexion
                con.close();
        }
    }

Este codigo lo probe con un procedimiento almacenado en Oracle, pero deberia ser trasparente para cualquier gestor de base de datos que se este usando.

Nota: en el DAO es necesario importar la libreria: java.sql.CallableStatement;

Comentarios

Opciones de visualización de comentarios

Seleccione la forma que prefiera para mostrar los comentarios y haga clic en «Guardar las opciones» para activar los cambios.

CallableStatement y Tablas temporales en SQLServer

Que hay javadicto, muy buena tu aportación!

Actualmente estoy trabajando en una aplicación en Java que manda llamar a procedimientos almacenados en SQLServer (SQLServer y Java? : | que combinación tan horrenda), esta aplicación no la comencé yo, ya estaba desarrollada con anterioridad por otra persona que ya dejó el proyecto en mis manos, prácticamente yo le doy mantenimiento, en fin ese no es el punto. El punto es que esta persona utilizaba un driver no libre :S pueden creer eso?? jajaja pues yo tmb me sorprendí, habiendo uno libre se fueron por el que no, total con este driver no era necesario el uso del CallableStatement, simplemente utilizaban un simple Statment y un executeQuery() y Zaz todo jalaba. Pero se llegó el día en que venció la fecha de driver no libre y pues tuvimos que migrar a el driver libre, lo implemente y creí que habría unas cuantas inconsistencias que no serían problema, pero da la casualidad de que no fue así, en primera por que al utilizar un Statement y un executeQuery() no era sufisciente, con el driver oficial de SQLServer había que hacerlo a su manera, entonces vi la opción del CallableStatement que me hace una llamada al procedimiento; para este tipo de Statement pueden existir parámetros de salida y de entrada (IN / OUT) esto es que podemos establecer los parámetros desde la llamada al procedimiento (esto en caso de saber que es lo que hace el procedimiento). Utilizando un execute() para dejar al manejador el tipo de resultado puesto que pueden ser conjuntos de resultados, recuentos de actualizaciones etc., y verificando que el tipo de cursor estuviera en TYPE_SCROLL_INSENSITIVE y la concurrencia en CONCUR_READ_ONLY, puede sonar sencillo pero en verdad se requiere esta combinación para poder llamar a un procedimiento y cualquier otra consulta con este driver. Con este cursor le decimos al ResultSet que podemos movernos en los resultados hacia abajo y hacia arriaba (esto también evita errores al utilizar el resultSet.last() o reultSet.first(), etc.), y la concurrencia solo indica que es de solo lectura, esto sólo es para evitar la alteración de los resultados con algún update o algo parecido, en caso de querer conservar los resultados intactos.

Bien arme todo los cambios para ver si todo esto funcionaba y NO : | a caray, me detuve a pensar y solo concluí en que algo en el procedimiento podía estar mal, puesto que ya me había leído toda la librería del JDBC de SQLServer y no me cabía la menor duda de que en Java estuviera haciendo algo mal. Me fuí con el experto en SQL a preguntarle que que pasaba con los procedimientos almacenados, revisamos un rato todo el proceso y concluimos que el problema estaba en crear tablas temporales : | quién lo iba a pensar, el procedimiento creaba una tabla temporal, insertaba todos los datos a retornar y devolvía un "Select * From tablaTemporal", la solución fue crear Variables como tablas temporales y santo remedio. La super desventaja era que teníamos que cambiar todos los procedimientos almacenados : ( y con eso ya no fue necesario usar el CallableStatement. Yo recomiendo utilizar el CallableStatement puesto que ayuda a controlar nuestras llamadas y resultados de procedimientos. Sin embargo yo me quedé con la espinita de saber por que no funcionaban las tablas temporales con un CallableStatement, si alguien lo sabe, dígamelo jaja :p.

javadicto mencionaste un punto fuerte para hacer uso de los procedimientos almacenados en Java desde cualquier Manejador de SQL, sin embargo te faltó hacer mención de el Por qué? es necesario que se haga así. suerte en tu participación y ps ya me aventé toda la historia jaja :p a ver si no se aburren.

Imagen de luxspes

Jdbc

Tanto los Statement como los CallableStatement son parte del estándar JDBC... independientemente del fabricante del driver, por lo que me sorprendería mucho encontrar que algún driver libre o no no tuviera soporte para alguno de los 2.

Hasta donde se, no debería haber ninguna relación entre usar tablas temporales dentro de un procedimiento y el modo como se llama a este procedimiento desde Jdbc... tal ves si nos mostraras el código que estuvo dándote problemas (asi como el mensaje de error que se producía, con su stacktrace completito) podríamos ayudarte a salir de dudas con respecto a la causa raíz del problema que tuviste. (queda claro la posibilidad de un bug en el driver JDBC que lo haga fallar cuando se usa tablas temporales, aunque esa posibilidad se me hace remota)

Imagen de javadicto

JDBC tiene su lado debil

Lo que comenta "luxspes" es cierto, lo bueno del JDBC es que pasa a ser trasparente para el gestor de la base de datos donde se este ejecutando solo con cambiar el Driver JDBC, bueno trasparente si usamos el SQL estandar, lo que me comentaron hace un par de dias en un curso de Spring al que asisto es que el lado debil del JDBC era como trata las Excepciones, ya que todo lo engloba como un SQLException osea una excepcion "checked" que hereda de "java.lang.Exception" el incoveniente de las excepciones checked es que el compilador de Java te obliga a tratar la excepción en un bloque catch o bien declarar la excepción en el throws para obligar al método que invoca a tratar la excepción. Para las unchecked o Runtime exceptions esto no es obligatorio.

El problema real de las excepciones "checked" es que los programadores pueden englobar codigo en una excepcion de tipo Exception ejemplo:

try
{
    //Mucho codigo
}
catch(Exception e)
{
     //No hace nada
    //Ni siquiera le dan un e.printStackTrace();
    //Ya de menos un e.getMessage()
}

El codigo anterior es comun verlo y sin querer pueden comerse excepciones de tipo Runtime y simplemete no tratarlas y nadie sabria que paso, el objetivo de las excepciones es detectarlas, tratarlas y recuperarse.

Obtener resultado de una función

Que tal Javadicto, tengo una par de dudas con respecto al llamado de procedimientos almacenados y funciones. En tu ejemplo muestras como llamar un procedimiento almacenado y como pasarle sus parametros, mis dudas son, ¿Es igual para llamar una función? y ¿Como obtienes el resultado de la función?

Saludos.

Imagen de javadicto

Hola rvillanuevap, con

Hola rvillanuevap, con respectos a tu primer pregunta, mi respuesta es SI, es lo mismo mandar a llamar un procedimiento almacenado o una funcion, y en cuanto a tu segunda pregunta, puedes obtener el resultado en un ResultSet y mostrarlo de una manera muy parecida como lo harias con una consulta, ejemplo:

public void ejecutarProcedimiento(...) throws Exception {
        Connection con = null;
        CallableStatement cs = null;
        try {

            con = ... //Obtenemos la conexion a la Bade de Datos

            cs = con.prepareCall(...);
           
            // Registramos el parámetro como de salida,
            // indicando que devuelve un número entero
            cs.registerOutParameter( 1, Types.INTEGER);

            // Ejecutamos el procedimiento almacenado
            ResultSet resultado = cs.executeQuery();
           
            // Leemos el parámetro de salida del procedimiento,
            // almacenándolo en un entero
            int i = cs.getInt(1);
            if (cs.wasNull()) {
                System.out.println("Resultado nulo");
            } else {
                System.out.print("Valor devuelto: " +i);
            }
           
        } catch (Exception e) {
            throw new Exception(e); //Propagamos la Excepcion
        } finally {
            if (cs != null) //Finalizamos cerrando el CallableStatement
            {
                cs.close();
            }

            if (con != null) //Finalizamos cerrando la conexion
            {
                con.close();
            }
        }
    }

Imagen de ezamudio

PCJ

Primero que nada, gracias por tu aportación. Esta cae en la categoría de las aportaciones técnicas, ya que incluyes bastante código, respecto del cual tengo algunas observaciones que te pueden ser de utilidad; algunas son simples optimizaciones pero otras son un poco más de diseño y mejores prácticas:

  • Estás utilizando StringBuffer de manera interna, cuando es más rápido usar StringBuilder, y usar + para concatenar, lo cual no es recomendable y sobre todo si ya tienes un StringBuffer, deberías estar usando append(). StringBuilder no es seguro para usar por varios hilos pero ya que es una variable interna no hay ningún problema de sincronización.
  • No entiendo para qué tener la clase Utils y la clase Constantes si a fin de cuentas sólo se está encapsulando un stored procedure muy específico, es decir, el método ejecutarProcedimiento sólo puede ser utilizado con ese stored procedure. Creo que si el objetivo es encapsular la llamada a un stored procedure, se puede hacer de manera más sencilla, todo en un método, sin tanto rodeo.
  • Finalmente, capturas Exception lo cual es considerado una mala práctica; debes capturar las excepciones más específicas, en este caso con capturar SQLException sería suficiente, además no haces nada con la excepción, solamente la propagas, por lo que ni siquiera es necesario el try-catch, podrías solamente tener try-finally.

El último punto es muy importante y hay discusiones al respecto en muchos sitios en internet; es considerado mala práctica interceptar Exception porque puedes estar interceptando cosas que realmente tu código no va manejar. Y es mala práctica declarar un método que arroja Exception porque obliga a quienes lo invoquen a interceptarla o declarar que la arrojan. Si lo que intentas en tu código es cerrar siempre la conexión y el CallableStatement, puedes usar solamente try-finally, no es necesario poner el catch, y en tu método declaras que arrojas SQLException. Pero bueno, ese de hecho es uno de los temas en el curso...

Imagen de javadicto

.

.

Imagen de Jhanno

Revisado PCJ

El manejo de excepciones debe considerarse lo más específico para una "buena práctica". Un buen aporte.

cual es el nombre del paquete que se usa en el codigo

la verdad es primera vez que estoy trabajando con conexion de oracle y se me ha complicado este asunto de llamar a procedimientos almacenados desde java y si alguien me puede ayudar con ese nombre del paquete que aparece en esta linea de codigo
StringBuffer sb = new StringBuffer("{call " + nombrePaquete + "." + nombreProcedimiento + "(");

saludos y gracias

Miguel Salazar

Hola que tal , tengo un problema a la hora de llamar un procedimiento almacenado en sql server que crea tablas temporales .

Si alguien me puede dar una mano con eso le agradecería infinitamente.

Gracias.

Hola javadicto

Hola amigo oye he estado viendo tus posts, tengo un problema con un proyecto y necesito un poco de ayuda, crees que podrías ayudarme?

preparecall

cuantos procedimientos puedo llamar con un preparecall alguien que me ayude