Exemplo de CallableStatement em Java

CallableStatement em Java é usado para chamar procedimentos armazenados a partir de um programa Java. Procedimentos Armazenados são um grupo de instruções que compilamos no banco de dados para alguma tarefa. Procedimentos armazenados são benéficos quando lidamos com várias tabelas em cenários complexos e, em vez de enviar várias consultas ao banco de dados, podemos enviar os dados necessários para o procedimento armazenado e ter a lógica executada no próprio servidor do banco de dados.

CallableStatement

A API JDBC fornece suporte para executar Procedimentos Armazenados por meio da interface CallableStatement. Procedimentos armazenados precisam ser escritos na sintaxe específica do banco de dados e, para este tutorial, vou usar o banco de dados Oracle. Vamos examinar as características padrão do CallableStatement com parâmetros de entrada e saída. Mais tarde, vamos explorar exemplos específicos do Oracle, como STRUCT e Cursor. Primeiro, vamos criar uma tabela para nossos programas de exemplo de CallableStatement com a seguinte consulta SQL. create_employee.sql

-- Para o banco de dados Oracle
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 primeiro criar uma classe de utilidade para obter o objeto de Conexão com o banco de dados Oracle. Certifique-se de que o arquivo JAR Oracle OJDBC esteja no caminho de construção do projeto. 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 {
			// Carregue a Classe do Driver
			Class.forName(DB_DRIVER_CLASS);

			// Crie a conexão agora
			con = DriverManager.getConnection(DB_URL,DB_USERNAME,DB_PASSWORD);
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		} catch (SQLException e) {
			e.printStackTrace();
		}
		return con;
	}
}

Exemplo de CallableStatement

Vamos escrever uma simples stored procedure para inserir dados na tabela Employee. 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 você pode ver, a procedure insertEmployee espera entradas do chamador que serão inseridas na tabela Employee. Se a instrução de inserção funcionar corretamente, ela retornará TRUE e, em caso de qualquer exceção, retornará FALSE. Vamos ver como podemos usar CallableStatement para executar a stored procedure insertEmployee e inserir dados do funcionário. 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;
		
		// Leia as Entradas do Usuário
		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);
			
			// Registre o parâmetro OUT antes de chamar a stored procedure
			stmt.registerOutParameter(6, java.sql.Types.VARCHAR);
			
			stmt.executeUpdate();
			
			// Leia o parâmetro OUT agora
			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 lendo a entrada do usuário para ser armazenada na tabela de Funcionários. A única diferença do PreparedStatement é a criação do CallableStatement através de “{call insertEmployee(?,?,?,?,?,?)}” e a configuração do parâmetro OUT com o método CallableStatement registerOutParameter(). Temos que registrar o parâmetro OUT antes de executar a stored procedure. Uma vez que a stored procedure é executada, podemos usar o método CallableStatement getXXX() para obter os dados do objeto OUT. Observe que ao registrar o parâmetro OUT, precisamos especificar o tipo de parâmetro OUT através de java.sql.Types. O código é genérico por natureza, então se tivermos a mesma stored procedure em outro banco de dados relacional como MySQL, podemos executá-la com este programa também. Abaixo está a saída quando estamos executando o programa de exemplo de CallableStatement acima várias vezes.

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

Observe que a segunda execução falhou porque o nome passado é maior do que o tamanho da coluna. Estamos tratando a exceção na stored procedure e retornando falso neste caso.

Exemplo de CallableStatement – Parâmetros OUT de Stored Procedure

Agora vamos escrever uma stored procedure para obter os dados do funcionário por id. O usuário irá inserir o id do funcionário e o programa irá exibir as informações do funcionário. 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;

Exemplo de programa Java CallableStatement usando a stored procedure getEmployee para ler os dados do funcionário é; 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;
		
		// Ler Entradas do Usuário
		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 o parâmetro OUT antes de chamar a stored procedure
			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();
			
			// ler o parâmetro OUT agora
			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();
			}
		}
	}

}

Novamente, o programa é genérico e funciona para qualquer banco de dados que tenha a mesma stored procedure. Vamos ver qual é a saída quando executamos o programa de exemplo CallableStatement acima.

Enter Employee ID (int):
1
Employee Name=Pankaj,Role=Developer,City=Bangalore,Country=India

Exemplo de CallableStatement – Stored Procedure Oracle CURSOR

Como estamos lendo as informações do funcionário por ID, estamos obtendo um único resultado e os parâmetros OUT funcionam bem para ler os dados. Mas se pesquisarmos por cargo ou país, podemos obter várias linhas e, nesse caso, podemos usar o CURSOR do Oracle para lê-las como um 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;
		
		// Ler Entradas do Usuário
		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 o parâmetro OUT antes de chamar a stored procedure
			stmt.registerOutParameter(2, OracleTypes.CURSOR);
			
			stmt.execute();
			
			// ler o parâmetro OUT agora
			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 classes específicas do Oracle OJDBC e não funcionará com outros bancos de dados. Estamos definindo o tipo de parâmetro OUT como OracleTypes.CURSOR e depois fazendo o cast para o objeto ResultSet. Outra parte do código é programação JDBC simples. Quando executamos o programa de exemplo de CallableStatement acima, obtemos a seguinte saída.

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

Sua saída pode variar dependendo dos dados em sua tabela de funcionários.

Exemplo de CallableStatement – Objeto e STRUCT do Oracle DB

Se olharmos para os procedimentos armazenados insertEmployee e getEmployee, estou tendo todos os parâmetros da tabela de funcionários no procedimento. Quando o número de colunas aumenta, isso pode levar a confusão e a mais erros. O banco de dados Oracle fornece a opção de criar objetos de banco de dados e podemos usar o STRUCT do Oracle para trabalhar com eles. Vamos primeiro definir o objeto DB Oracle para as colunas da tabela de funcionários. 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)
  
  );

Agora vamos reescrever o procedimento armazenado insertEmployee usando 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;

Vamos ver como podemos chamar o procedimento armazenado insertEmployeeObject no 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;
		
		//Criar Array de Objetos para chamada de Procedimento Armazenado
		Object[] empObjArray = new Object[5];
		//Ler Entradas do Usuário
		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 o parâmetro DE SAÍDA antes de chamar o procedimento armazenado
			stmt.registerOutParameter(2, java.sql.Types.VARCHAR);
			
			stmt.executeUpdate();
			
			//ler o parâmetro DE SAÍDA agora
			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();
			}
		}
	}

}

Primeiramente, estamos criando um array de objetos com o mesmo comprimento do objeto de banco de dados EMPLOYEE_OBJ. Em seguida, definimos valores de acordo com as variáveis do objeto EMPLOYEE_OBJ. Isso é muito importante, caso contrário, os dados serão inseridos nas colunas erradas. Em seguida, criamos o objeto oracle.sql.STRUCT com a ajuda de oracle.sql.StructDescriptor e nosso array de objetos. Uma vez que o objeto STRUCT é criado, o definimos como parâmetro de ENTRADA para o procedimento armazenado, registramos o parâmetro DE SAÍDA e o executamos. Este código está fortemente acoplado com a API OJDBC e não funcionará para outros bancos de dados. Aqui está a saída ao executar 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 usar o objeto do banco de dados como parâmetro DE SAÍDA também e lê-lo para obter os valores do banco de dados. Isso é tudo para o exemplo de CallableStatement em Java para executar Procedimentos Armazenados. Espero que você tenha aprendido algo com isso.

Source:
https://www.digitalocean.com/community/tutorials/callablestatement-in-java-example