Java中的CallableStatement示例

在Java中,CallableStatement用于从Java程序中调用存储过程。存储过程是一组我们在数据库中编译的语句,用于执行某些任务。当我们需要处理涉及多个表的复杂场景时,存储过程非常有用。与将多个查询发送到数据库不同,我们可以将所需数据发送到存储过程,并在数据库服务器中执行逻辑。

CallableStatement

JDBC API提供了通过CallableStatement接口执行存储过程的支持。存储过程需要使用特定于数据库的语法编写,在本教程中,我将使用Oracle数据库。我们将研究CallableStatement的标准功能,包括IN和OUT参数。稍后,我们将查看Oracle特定的STRUCTCursor示例。首先,让我们使用以下SQL查询语句为我们的CallableStatement示例程序创建一个表。create_employee.sql

-- 对于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")
  );

讓我們首先創建一個實用類來獲取 Oracle 數據庫連接對象。確保 Oracle OJDBC jar 已添加到項目的構建路徑中。 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 {
			// 加載驅動類
			Class.forName(DB_DRIVER_CLASS);

			// 創建連接
			con = DriverManager.getConnection(DB_URL,DB_USERNAME,DB_PASSWORD);
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		} catch (SQLException e) {
			e.printStackTrace();
		}
		return con;
	}
}

CallableStatement 示例

讓我們編寫一個簡單的存儲過程,將數據插入到 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;

如您所見,insertEmployee 過程期望來自調用者的輸入,這些輸入將被插入到 Employee 表中。如果插入語句運行正常,它將返回 TRUE,如果出現任何異常,則返回 FALSE。讓我們看看如何使用 CallableStatement 執行 insertEmployee 存儲過程以插入員工數據。 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;
		
		//讀取用戶輸入
		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);
			
			//在調用存儲過程之前註冊 OUT 參數
			stmt.registerOutParameter(6, java.sql.Types.VARCHAR);
			
			stmt.executeUpdate();
			
			//現在讀取 OUT 參數
			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();
			}
		}
	}

}

我們正在讀取用戶輸入以存儲在員工表中。與 PreparedStatement 唯一不同的是通過 “{call insertEmployee(?,?,?,?,?,?)}” 創建 CallableStatement 並使用 CallableStatement registerOutParameter() 方法設置 OUT 參數。在執行存儲過程之前,我們必須註冊 OUT 參數。一旦執行了存儲過程,我們可以使用 CallableStatement getXXX() 方法來獲取 OUT 物件數據。請注意,在註冊 OUT 參數時,我們需要通過 java.sql.Types 指定 OUT 參數的類型。代碼具有通用性,因此如果在其他關係數據庫中有相同的存儲過程,比如 MySQL,我們也可以使用這個程序來執行它們。以下是在多次執行上述 CallableStatement 示例程序時的輸出。

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

請注意,第二次執行失敗,因為傳遞的名稱大於列大小。我們在存儲過程中捕獲了異常,在這種情況下返回 false。

CallableStatement 示例 – 存儲過程 OUT 參數

現在讓我們編寫一個存儲過程,通過 id 獲取員工數據。用戶將輸入員工 id,程序將顯示員工信息。 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;

Java CallableStatement 範例程式,使用 getEmployee 儲存過程來讀取員工資料是;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;
		
		// 讀取使用者輸入
		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);
			
			// 在呼叫儲存過程之前註冊 OUT 參數
			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();
			
			// 現在讀取 OUT 參數
			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();
			}
		}
	}

}

再次強調,該程式是通用的,適用於具有相同儲存過程的任何資料庫。讓我們看看當執行上述 CallableStatement 範例程式時的輸出。

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

CallableStatement 範例 – 儲存過程 Oracle CURSOR

由於我們是通過 ID 讀取員工資訊,因此我們得到單一結果,OUT 參數可以很好地用於讀取資料。但如果我們按角色或國家搜索,可能會得到多行結果,在這種情況下,我們可以使用 Oracle CURSOR 來讀取它們,就像結果集一樣。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;
		
		// 讀取使用者輸入
		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);
			
			// 在呼叫儲存過程之前註冊 OUT 參數
			stmt.registerOutParameter(2, OracleTypes.CURSOR);
			
			stmt.execute();
			
			// 現在讀取 OUT 參數
			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();
			}
		}
	}

}

這個程式使用了 Oracle OJDBC 專用類,並且無法與其他數據庫一起使用。我們將 OUT 參數類型設置為 OracleTypes.CURSOR,然後將其轉換為 ResultSet 對象。代碼的其他部分是簡單的 JDBC 編程。當我們執行上面的 CallableStatement 示例程序時,我們會得到以下輸出。

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

您的輸出可能會根據 Employee 表中的數據而變化。

CallableStatement 示例 – Oracle DB 對象和 STRUCT

如果您查看 insertEmployeegetEmployee 存儲過程,我在存儲過程中擁有 Employee 表的所有參數。當列數增加時,這可能會導致混淆和更多錯誤。Oracle 數據庫提供了創建數據庫對象的選項,我們可以使用 Oracle STRUCT 來處理它們。讓我們首先為 Employee 表列定義 Oracle DB 對象。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)
  
  );

現在讓我們使用 EMPLOYEE_OBJ 重寫 insertEmployee 存儲過程。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;

讓我們看看如何在 java 程式中調用 insertEmployeeObject 存儲過程。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;
		
		//建立物件陣列以供呼叫儲存過程使用
		Object[] empObjArray = new Object[5];
		//讀取使用者輸入
		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);
			
			//在呼叫儲存過程之前註冊OUT參數
			stmt.registerOutParameter(2, java.sql.Types.VARCHAR);
			
			stmt.executeUpdate();
			
			//現在讀取OUT參數
			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();
			}
		}
	}

}

首先,我們建立了與EMPLOYEE_OBJ資料庫物件相同長度的物件陣列。然後,我們根據EMPLOYEE_OBJ物件變數設置值。這一點非常重要,否則資料將插入到錯誤的欄位中。然後,我們使用oracle.sql.StructDescriptor和我們的物件陣列來創建oracle.sql.STRUCT物件。一旦建立了STRUCT物件,我們將其設置為儲存過程的IN參數,註冊OUT參數並執行它。此代碼與OJDBC API緊密耦合,不適用於其他資料庫。當我們執行此程式時,這是輸出。

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

我們也可以將資料庫物件用作OUT參數,並讀取它以從資料庫中獲取值。這就是有關於在Java中執行儲存過程的CallableStatement的示例,希望你從中學到了一些東西。

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