CallableStatement en Java se utiliza para llamar a un procedimiento almacenado desde un programa en Java. Los procedimientos almacenados son un conjunto de declaraciones que compilamos en la base de datos para realizar alguna tarea. Los procedimientos almacenados son beneficiosos cuando estamos trabajando con múltiples tablas en un escenario complejo, y en lugar de enviar múltiples consultas a la base de datos, podemos enviar los datos requeridos al procedimiento almacenado y ejecutar la lógica en el propio servidor de la base de datos.
CallableStatement
La API JDBC proporciona soporte para ejecutar procedimientos almacenados a través de la interfaz
CallableStatement
. Los procedimientos almacenados deben estar escritos en la sintaxis específica de la base de datos y, para este tutorial, utilizaré la base de datos Oracle. Examinaremos las características estándar de CallableStatement con parámetros de entrada y salida. Más adelante, veremos ejemplos específicos de Oracle con STRUCT y Cursor. Primero, creemos una tabla para nuestros programas de ejemplo de CallableStatement con la siguiente consulta SQL. create_employee.sql
-- Para Oracle DB
CREATE TABLE EMPLOYEE
(
"EMPID" NUMBER NOT NULL ENABLE,
"NAME" VARCHAR2(10 BYTE) DEFAULT NULL,
"ROLE" VARCHAR2(10 BYTE) DEFAULT NULL,
"CITY" VARCHAR2(10 BYTE) DEFAULT NULL,
"COUNTRY" VARCHAR2(10 BYTE) DEFAULT NULL,
PRIMARY KEY ("EMPID")
);
Vamos a crear primero una clase de utilidad para obtener el objeto de conexión a la base de datos de Oracle. Asegúrate de que el archivo jar de Oracle OJDBC esté en la ruta de compilación del proyecto. DBConnection.java
package com.journaldev.jdbc.storedproc;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
public class DBConnection {
private static final String DB_DRIVER_CLASS = "oracle.jdbc.driver.OracleDriver";
private static final String DB_URL = "jdbc:oracle:thin:@localhost:1521:orcl";
private static final String DB_USERNAME = "HR";
private static final String DB_PASSWORD = "oracle";
public static Connection getConnection() {
Connection con = null;
try {
// cargar la Clase del Controlador
Class.forName(DB_DRIVER_CLASS);
// crear la conexión ahora
con = DriverManager.getConnection(DB_URL,DB_USERNAME,DB_PASSWORD);
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SQLException e) {
e.printStackTrace();
}
return con;
}
}
Ejemplo de CallableStatement
Vamos a escribir un procedimiento almacenado simple para insertar datos en la tabla de Empleados. insertEmployee.sql
CREATE OR REPLACE PROCEDURE insertEmployee
(in_id IN EMPLOYEE.EMPID%TYPE,
in_name IN EMPLOYEE.NAME%TYPE,
in_role IN EMPLOYEE.ROLE%TYPE,
in_city IN EMPLOYEE.CITY%TYPE,
in_country IN EMPLOYEE.COUNTRY%TYPE,
out_result OUT VARCHAR2)
AS
BEGIN
INSERT INTO EMPLOYEE (EMPID, NAME, ROLE, CITY, COUNTRY)
values (in_id,in_name,in_role,in_city,in_country);
commit;
out_result := 'TRUE';
EXCEPTION
WHEN OTHERS THEN
out_result := 'FALSE';
ROLLBACK;
END;
Como puedes ver, el procedimiento insertEmployee espera entradas del llamante que serán insertadas en la tabla de Empleados. Si la declaración de inserción funciona correctamente, devuelve VERDADERO y en caso de cualquier excepción devuelve FALSO. Veamos cómo podemos usar CallableStatement
para ejecutar el procedimiento almacenado insertEmployee
para insertar datos de empleados. JDBCStoredProcedureWrite.java
package com.journaldev.jdbc.storedproc;
import java.sql.CallableStatement;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Scanner;
public class JDBCStoredProcedureWrite {
public static void main(String[] args) {
Connection con = null;
CallableStatement stmt = null;
// Leer Entradas de Usuario
Scanner input = new Scanner(System.in);
System.out.println("Enter Employee ID (int):");
int id = Integer.parseInt(input.nextLine());
System.out.println("Enter Employee Name:");
String name = input.nextLine();
System.out.println("Enter Employee Role:");
String role = input.nextLine();
System.out.println("Enter Employee City:");
String city = input.nextLine();
System.out.println("Enter Employee Country:");
String country = input.nextLine();
try{
con = DBConnection.getConnection();
stmt = con.prepareCall("{call insertEmployee(?,?,?,?,?,?)}");
stmt.setInt(1, id);
stmt.setString(2, name);
stmt.setString(3, role);
stmt.setString(4, city);
stmt.setString(5, country);
// registrar el parámetro OUT antes de llamar al procedimiento almacenado
stmt.registerOutParameter(6, java.sql.Types.VARCHAR);
stmt.executeUpdate();
// leer el parámetro OUT ahora
String result = stmt.getString(6);
System.out.println("Employee Record Save Success::"+result);
}catch(Exception e){
e.printStackTrace();
}finally{
try {
stmt.close();
con.close();
input.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
Estamos leyendo la entrada del usuario para almacenarla en la tabla de Empleados. Lo único diferente de PreparedStatement
es la creación de CallableStatement a través de “{call insertEmployee(?,?,?,?,?,?)}
” y el establecimiento del parámetro OUT con el método CallableStatement registerOutParameter()
. Debemos registrar el parámetro OUT antes de ejecutar el procedimiento almacenado. Una vez que se ejecuta el procedimiento almacenado, podemos usar el método CallableStatement getXXX()
para obtener los datos del objeto OUT. Nótese que al registrar el parámetro OUT, necesitamos especificar el tipo de parámetro OUT a través de java.sql.Types
. El código es genérico en naturaleza, por lo que si tenemos el mismo procedimiento almacenado en otra base de datos relacional como MySQL, también podemos ejecutarlos con este programa. A continuación se muestra la salida cuando ejecutamos el programa de ejemplo de CallableStatement varias veces.
Enter Employee ID (int):
1
Enter Employee Name:
Pankaj
Enter Employee Role:
Developer
Enter Employee City:
Bangalore
Enter Employee Country:
India
Employee Record Save Success::TRUE
-----
Enter Employee ID (int):
2
Enter Employee Name:
Pankaj Kumar
Enter Employee Role:
CEO
Enter Employee City:
San Jose
Enter Employee Country:
USA
Employee Record Save Success::FALSE
Se observe que la segunda ejecución falló porque el nombre pasado es mayor que el tamaño de la columna. Estamos consumiendo la excepción en el procedimiento almacenado y devolviendo falso en este caso.
Ejemplo de CallableStatement – Parámetros OUT del Procedimiento Almacenado
Ahora escribamos un procedimiento almacenado para obtener los datos del empleado por id. El usuario ingresará el id del empleado y el programa mostrará la información del empleado. getEmployee.sql
create or replace
PROCEDURE getEmployee
(in_id IN EMPLOYEE.EMPID%TYPE,
out_name OUT EMPLOYEE.NAME%TYPE,
out_role OUT EMPLOYEE.ROLE%TYPE,
out_city OUT EMPLOYEE.CITY%TYPE,
out_country OUT EMPLOYEE.COUNTRY%TYPE
)
AS
BEGIN
SELECT NAME, ROLE, CITY, COUNTRY
INTO out_name, out_role, out_city, out_country
FROM EMPLOYEE
WHERE EMPID = in_id;
END;
El programa de ejemplo de Java CallableStatement que utiliza el procedimiento almacenado getEmployee para leer los datos del empleado es; JDBCStoredProcedureRead.java
package com.journaldev.jdbc.storedproc;
import java.sql.CallableStatement;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Scanner;
public class JDBCStoredProcedureRead {
public static void main(String[] args) {
Connection con = null;
CallableStatement stmt = null;
// Leer entradas de usuario
Scanner input = new Scanner(System.in);
System.out.println("Enter Employee ID (int):");
int id = Integer.parseInt(input.nextLine());
try{
con = DBConnection.getConnection();
stmt = con.prepareCall("{call getEmployee(?,?,?,?,?)}");
stmt.setInt(1, id);
// registrar el parámetro OUT antes de llamar al procedimiento almacenado
stmt.registerOutParameter(2, java.sql.Types.VARCHAR);
stmt.registerOutParameter(3, java.sql.Types.VARCHAR);
stmt.registerOutParameter(4, java.sql.Types.VARCHAR);
stmt.registerOutParameter(5, java.sql.Types.VARCHAR);
stmt.execute();
// leer el parámetro OUT ahora
String name = stmt.getString(2);
String role = stmt.getString(3);
String city = stmt.getString(4);
String country = stmt.getString(5);
if(name !=null){
System.out.println("Employee Name="+name+",Role="+role+",City="+city+",Country="+country);
}else{
System.out.println("Employee Not Found with ID"+id);
}
}catch(Exception e){
e.printStackTrace();
}finally{
try {
stmt.close();
con.close();
input.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
Una vez más, el programa es genérico y funciona para cualquier base de datos que tenga el mismo procedimiento almacenado. Veamos cuál es la salida cuando ejecutamos el programa de ejemplo de CallableStatement anterior.
Enter Employee ID (int):
1
Employee Name=Pankaj,Role=Developer,City=Bangalore,Country=India
Ejemplo de CallableStatement – Procedimiento almacenado Oracle CURSOR
Dado que estamos leyendo la información del empleado a través del ID, obtenemos un resultado único y los parámetros OUT funcionan bien para leer los datos. Pero si buscamos por rol o país, podríamos obtener múltiples filas y en ese caso podemos usar el CURSOR de Oracle para leerlos como un conjunto de resultados. getEmployeeByRole.sql
create or replace
PROCEDURE getEmployeeByRole
(in_role IN EMPLOYEE.ROLE%TYPE,
out_cursor_emps OUT SYS_REFCURSOR
)
AS
BEGIN
OPEN out_cursor_emps FOR
SELECT EMPID, NAME, CITY, COUNTRY
FROM EMPLOYEE
WHERE ROLE = in_role;
END;
JDBCStoredProcedureCursor.java
package com.journaldev.jdbc.storedproc;
import java.sql.CallableStatement;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Scanner;
import oracle.jdbc.OracleTypes;
public class JDBCStoredProcedureCursor {
public static void main(String[] args) {
Connection con = null;
CallableStatement stmt = null;
ResultSet rs = null;
// Leer entradas de usuario
Scanner input = new Scanner(System.in);
System.out.println("Enter Employee Role:");
String role = input.nextLine();
try{
con = DBConnection.getConnection();
stmt = con.prepareCall("{call getEmployeeByRole(?,?)}");
stmt.setString(1, role);
// registrar el parámetro OUT antes de llamar al procedimiento almacenado
stmt.registerOutParameter(2, OracleTypes.CURSOR);
stmt.execute();
// leer el parámetro OUT ahora
rs = (ResultSet) stmt.getObject(2);
while(rs.next()){
System.out.println("Employee ID="+rs.getInt("empId")+",Name="+rs.getString("name")+
",Role="+role+",City="+rs.getString("city")+
",Country="+rs.getString("country"));
}
}catch(Exception e){
e.printStackTrace();
}finally{
try {
rs.close();
stmt.close();
con.close();
input.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
Este programa está utilizando clases específicas de Oracle OJDBC y no funcionará con otras bases de datos. Estamos estableciendo el tipo de parámetro OUT como OracleTypes.CURSOR
y luego lo convertimos en un objeto ResultSet
. Otra parte del código es simplemente programación JDBC. Cuando ejecutamos el programa de ejemplo de CallableStatement anterior, obtenemos la siguiente salida.
Enter Employee Role:
Developer
Employee ID=5,Name=Kumar,Role=Developer,City=San Jose,Country=USA
Employee ID=1,Name=Pankaj,Role=Developer,City=Bangalore,Country=India
Su salida puede variar dependiendo de los datos en su tabla de Empleados.
Ejemplo de CallableStatement – Objeto DB Oracle y STRUCT
Si observamos los procedimientos almacenados insertEmployee
y getEmployee
, tengo todos los parámetros de la tabla de Empleados en el procedimiento. Cuando el número de columnas crece, esto puede llevar a confusión y a más errores. La base de datos Oracle proporciona la opción de crear un objeto de base de datos y podemos usar Oracle STRUCT para trabajar con ellos. Primero definamos el objeto DB Oracle para las columnas de la tabla de Empleados. EMPLOYEE_OBJ.sql
create or replace TYPE EMPLOYEE_OBJ AS OBJECT
(
EMPID NUMBER,
NAME VARCHAR2(10),
ROLE VARCHAR2(10),
CITY VARCHAR2(10),
COUNTRY VARCHAR2(10)
);
Ahora reescribamos el procedimiento almacenado insertEmployee utilizando EMPLOYEE_OBJ. insertEmployeeObject.sql
CREATE OR REPLACE PROCEDURE insertEmployeeObject
(IN_EMPLOYEE_OBJ IN EMPLOYEE_OBJ,
out_result OUT VARCHAR2)
AS
BEGIN
INSERT INTO EMPLOYEE (EMPID, NAME, ROLE, CITY, COUNTRY) values
(IN_EMPLOYEE_OBJ.EMPID, IN_EMPLOYEE_OBJ.NAME, IN_EMPLOYEE_OBJ.ROLE, IN_EMPLOYEE_OBJ.CITY, IN_EMPLOYEE_OBJ.COUNTRY);
commit;
out_result := 'TRUE';
EXCEPTION
WHEN OTHERS THEN
out_result := 'FALSE';
ROLLBACK;
END;
Veamos cómo podemos llamar al procedimiento almacenado insertEmployeeObject
en un programa Java. JDBCStoredProcedureOracleStruct.java
package com.journaldev.jdbc.storedproc;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Scanner;
import oracle.jdbc.OracleCallableStatement;
import oracle.sql.STRUCT;
import oracle.sql.StructDescriptor;
public class JDBCStoredProcedureOracleStruct {
public static void main(String[] args) {
Connection con = null;
OracleCallableStatement stmt = null;
//Crear un array de objetos para la llamada al procedimiento almacenado
Object[] empObjArray = new Object[5];
//Leer las entradas del usuario
Scanner input = new Scanner(System.in);
System.out.println("Enter Employee ID (int):");
empObjArray[0] = Integer.parseInt(input.nextLine());
System.out.println("Enter Employee Name:");
empObjArray[1] = input.nextLine();
System.out.println("Enter Employee Role:");
empObjArray[2] = input.nextLine();
System.out.println("Enter Employee City:");
empObjArray[3] = input.nextLine();
System.out.println("Enter Employee Country:");
empObjArray[4] = input.nextLine();
try{
con = DBConnection.getConnection();
StructDescriptor empStructDesc = StructDescriptor.createDescriptor("EMPLOYEE_OBJ", con);
STRUCT empStruct = new STRUCT(empStructDesc, con, empObjArray);
stmt = (OracleCallableStatement) con.prepareCall("{call insertEmployeeObject(?,?)}");
stmt.setSTRUCT(1, empStruct);
//Registrar el parámetro OUT antes de llamar al procedimiento almacenado
stmt.registerOutParameter(2, java.sql.Types.VARCHAR);
stmt.executeUpdate();
//Leer el parámetro OUT ahora
String result = stmt.getString(2);
System.out.println("Employee Record Save Success::"+result);
}catch(Exception e){
e.printStackTrace();
}finally{
try {
stmt.close();
con.close();
input.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
En primer lugar, creamos un array de objetos con la misma longitud que el objeto de base de datos EMPLOYEE_OBJ. Luego, establecemos valores de acuerdo con las variables del objeto EMPLOYEE_OBJ. Esto es muy importante, de lo contrario, los datos se insertarán en columnas incorrectas. A continuación, creamos un objeto `oracle.sql.STRUCT` con la ayuda de `oracle.sql.StructDescriptor` y nuestro array de objetos. Una vez que se crea el objeto STRUCT, lo establecemos como parámetro de entrada para el procedimiento almacenado, registramos el parámetro OUT y lo ejecutamos. Este código está estrechamente acoplado con la API OJDBC y no funcionará para otras bases de datos. Aquí está la salida cuando ejecutamos este programa.
Enter Employee ID (int):
5
Enter Employee Name:
Kumar
Enter Employee Role:
Developer
Enter Employee City:
San Jose
Enter Employee Country:
USA
Employee Record Save Success::TRUE
Podemos utilizar el objeto de base de datos como parámetro OUT también y leerlo para obtener los valores de la base de datos. Eso es todo para el ejemplo de CallableStatement en Java para ejecutar procedimientos almacenados, espero que hayas aprendido algo de ello.
Source:
https://www.digitalocean.com/community/tutorials/callablestatement-in-java-example