Exemplo de CallableStatement em Java

CallableStatement em Java é usado para chamar procedimentos armazenados a partir de um programa Java. Procedimentos Armazenados são grupos de declarações que compilamos no banco de dados para realizar 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 ao 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 explorar as características padrão do CallableStatement com parâmetros de entrada e saída. Mais tarde, veremos exemplos específicos do Oracle, como STRUCT e Cursor. Primeiro, criaremos uma tabela para nossos programas de exemplo do CallableStatement com a seguinte 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 primeiro criar uma classe de utilidade para obter o objeto de Conexão com o banco de dados Oracle. Certifique-se de que o jar do 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 {
			// carregar a Classe do Driver
			Class.forName(DB_DRIVER_CLASS);

			// criar 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 está esperando entradas do chamador que serão inseridas na tabela Employee. Se o comando de inserção funcionar corretamente, ele retornará TRUE e, em caso de exceção, retornará FALSE. Vamos ver como podemos usar CallableStatement para executar a stored procedure insertEmployee e inserir os 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;
		
		// Ler 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);
			
			// registrar o parâmetro OUT antes de chamar a stored procedure
			stmt.registerOutParameter(6, java.sql.Types.VARCHAR);
			
			stmt.executeUpdate();
			
			// ler 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 em relação a PreparedStatement é a criação de um CallableStatement através de “{call insertEmployee(?,?,?,?,?,?)}” e a definição do parâmetro OUT com o método CallableStatement registerOutParameter(). Devemos 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, então se tivermos a mesma stored procedure em outro banco de dados relacional como o MySQL, podemos executá-la com este programa também. Abaixo está a saída quando executamos o programa de exemplo de CallableStatement 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 fornecido é maior do que o tamanho da coluna. Estamos tratando a exceção na stored procedure e retornando falso nesse caso.

Exemplo de CallableStatement – Parâmetros OUT de Stored Procedure

Agora vamos escrever uma stored procedure para obter os dados do funcionário pelo ID. O usuário informará o ID do funcionário e o programa 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;

O exemplo do programa Java CallableStatement usando o procedimento armazenado 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 o procedimento armazenado 
			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 o mesmo procedimento armazenado. Vamos ver qual é a saída quando executamos o programa de exemplo de CallableStatement acima.

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

Exemplo de CallableStatement – Procedimento Armazenado 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 o procedimento armazenado 
			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 configurando o tipo de parâmetro OUT como OracleTypes.CURSOR e, em seguida, convertendo-o para um objeto ResultSet. Outra parte do código é programação JDBC simples. Quando executamos o programa de exemplo CallableStatement acima, obtemos a saída abaixo.

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 você olhar 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 um objeto de banco de dados e podemos usar o STRUCT do Oracle para trabalhar com eles. Vamos primeiro definir o objeto de banco de dados 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 Matriz 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 OUT antes de chamar o procedimento armazenado
			stmt.registerOutParameter(2, java.sql.Types.VARCHAR);
			
			stmt.executeUpdate();
			
			//Ler o parâmetro OUT 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();
			}
		}
	}

}

Primeiro, estamos criando uma matriz de objetos com o mesmo comprimento do objeto de banco de dados EMPLOYEE_OBJ. Em seguida, estamos definindo 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, estamos criando o objeto oracle.sql.STRUCT com a ajuda de oracle.sql.StructDescriptor e nossa matriz de objetos. Uma vez que o objeto STRUCT é criado, estamos definindo-o como parâmetro IN para o procedimento armazenado, registrando o parâmetro OUT e executando-o. Este código está intimamente ligado com a API OJDBC e não funcionará para outros bancos de dados. Aqui está a saída quando executamos 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 OUT 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