Java JDBC CallableStatement
Invoca procedimientos almacenados desde Java con CallableStatement.
Un CallableStatement invoca un procedimiento almacenado o función que reside dentro de la base de datos. Extiende PreparedStatement, por lo que tiene el mismo enlace de marcadores ? — además de la capacidad de registrar parámetros OUT que el procedimiento te devuelve. Úsalo cuando la lógica de negocio está implementada en la base de datos en lugar de en Java.
Este capítulo cubre la sintaxis de escape JDBC para llamadas, las tres direcciones de parámetros (IN, OUT, INOUT), por qué los parámetros OUT necesitan un tipo registrado, y el orden estricto en que debes llamar a los métodos.
La sintaxis de escape JDBC
Escribes la llamada usando la sintaxis de llaves portátil, que cada driver traduce al dialecto de su proveedor:
// procedure with two IN parameters
CallableStatement cs = conn.prepareCall("{call add_customer(?, ?)}");
// function returning a value, with one IN parameter
CallableStatement fn = conn.prepareCall("{? = call total_orders(?)}");Las llaves significan que no tienes que saber si el proveedor lo escribe como CALL, EXEC o BEGIN ... END.
Parámetros IN, OUT e INOUT
Un parámetro de procedimiento tiene una dirección:
- IN — tú lo suministras:
cs.setInt(1, customerId), exactamente como en unPreparedStatement. - OUT — el procedimiento lo rellena; debes registrar su tipo primero y luego leerlo después de la ejecución.
- INOUT — ambos: establécelo, luego regístralo y después léelo.
CallableStatement cs = conn.prepareCall("{call get_balance(?, ?)}");
cs.setInt(1, accountId); // IN
cs.registerOutParameter(2, java.sql.Types.DECIMAL); // OUT — declare its type
cs.execute();
BigDecimal balance = cs.getBigDecimal(2); // read it backCuando un procedimiento produce una tabla completa en lugar de un valor único, devuelve un ResultSet: llama a cs.executeQuery() (o usa el boolean de cs.execute() más cs.getResultSet()) e itéralo exactamente igual que harías con un ResultSet de un Statement ordinario.
Por qué registerOutParameter necesita un tipo
JDBC tiene que saber cómo interpretar los bytes que la base de datos devuelve antes de que se ejecute la llamada, por lo que nombras el tipo SQL con una constante java.sql.Types. Si este tipo es incorrecto, el getXxx posterior fallará o convertirá mal. El orden es fijo: registrar OUT → establecer IN → ejecutar → getXxx.
Un ejemplo completo: construir la llamada y registrar OUT
Este programa construye ambas cadenas de llamada con sintaxis de escape y recorre el registro de parámetros OUT con los códigos java.sql.Types que necesita — el protocolo completo de llamada, expresado sin una base de datos activa.
Lo que hay que extraer de la ejecución:
- Las llaves
{call proc(?, ?)}son la sintaxis de escape portátil. Escribes la misma cadena independientemente del proveedor, y el driver la reescribe — esto es lo que mantiene las llamadas a procedimientos almacenados independientes de la base de datos. - La forma
{? = call fn(?)}es para funciones que devuelven un valor: el?inicial es el slot de retorno, registrado como parámetro OUT en el índice 1, con los argumentos reales a continuación. registerOutParametertoma una constantejava.sql.Types(INTEGERes 4,DECIMALes 3). Es el mismo vocabulario de tipos de capítulos anteriores — JDBC lo reutiliza en todos los lugares donde debe nombrar un tipo SQL sin un valor Java disponible.- El tipo que registras debe coincidir con lo que el procedimiento devuelve realmente; de lo contrario, el posterior
getInt/getBigDecimalleerá mal los bytes. Registrar es una promesa sobre la forma del resultado. - El orden del protocolo es estricto y vale la pena memorizar: registrar parámetros OUT, establecer parámetros IN,
execute(), luego leer valores OUT congetXxx(index). El ejemplo describe esa secuencia porque hacerlo fuera de orden es la causa habitual de los errores "parameter not registered".