JDBC (Java Database Connectivity) 定義了統一的資料庫操作介面,各資料庫廠商提供驅動程式實作。呼叫 Oracle 預存程序時,核心類別為 java.sql.CallableStatement。本文說明基本呼叫流程、OUT 參數處理、REF CURSOR 結果集回傳,並釐清 execute()、executeUpdate()、executeQuery() 三者在預存程序場景下的異同。
Class.forName("oracle.jdbc.OracleDriver")DriverManager.getConnection(url, user, pwd)conn.prepareCall("{call procedure_name(?, ?)}")setXxx() 設定 IN 參數,registerOutParameter() 註冊 OUT 參數execute() / executeUpdate() / executeQuery()getXxx() 讀取 OUT 參數,getObject() 取得 REF CURSORCREATE OR REPLACE PROCEDURE sp_get_value (
p_out OUT NUMBER
) AS
BEGIN
p_out := 10;
END;
/
String sql = "{call sp_get_value(?)}";
try (Connection conn = DriverManager.getConnection(url, user, pwd);
CallableStatement cs = conn.prepareCall(sql)) {
cs.registerOutParameter(1, Types.INTEGER);
cs.executeUpdate(); // 或 execute()、executeQuery() 均可
int result = cs.getInt(1); // 取得 10
System.out.println("回傳值:" + result);
}
CREATE OR REPLACE PROCEDURE sp_get_dept (
p_cursor OUT SYS_REFCURSOR
) AS
BEGIN
OPEN p_cursor FOR SELECT deptno, dname FROM dept;
END;
/
提示: Oracle 12c+ 內建 SYS_REFCURSOR 型別,無需自訂套件。
String sql = "{call sp_get_dept(?)}";
try (Connection conn = DriverManager.getConnection(url, user, pwd);
CallableStatement cs = conn.prepareCall(sql)) {
cs.registerOutParameter(1, OracleTypes.CURSOR);
cs.execute(); // 三種執行方法皆可
try (ResultSet rs = (ResultSet) cs.getObject(1)) {
while (rs.next()) {
System.out.println(rs.getInt("deptno") + " - " + rs.getString("dname"));
}
}
}
| 方法 | 回傳型別 | 適用場景 | 預存程序場景行為 |
|---|---|---|---|
execute() | boolean | 通用,可能產生多結果集 | 最通用,支援 OUT 參數與 REF CURSOR,推薦使用 |
executeUpdate() | int (影響筆數) | DML (INSERT/UPDATE/DELETE) | 可執行預存程序,忽略結果集,僅回傳更新筆數(通常為 0 或 -1) |
executeQuery() | ResultSet | SELECT 查詢 | 可執行預存程序,若有 REF CURSOR 會拋出例外(預期單一 ResultSet),不建議使用 |
結論: 呼叫預存程序時,統一使用 execute() 最為穩健,可同時處理 OUT 參數與多重 REF CURSOR。
| 錯誤現象 | 原因 | 解決 |
|---|---|---|
ORA-06550: PLS-00306 | 參數數量/型別不匹配 | 檢查 Java 端 registerOutParameter 索引、型別與 PL/SQL 宣告一致 |
ClassCastException: cannot cast to ResultSet | 未正確註冊 CURSOR 型別 | 確認 cs.registerOutParameter(idx, OracleTypes.CURSOR) |
| 游標已關閉 | ResultSet 使用後未正確關閉,或連線已提交/關閉 | 在同一交易/連線存活期間處理完 ResultSet,使用 try-with-resources |
| 驅動類別找不到 | Classpath 缺少 ojdbc.jar | Maven 引入 com.oracle.database.jdbc:ojdbc11 或手動加入 JAR |
cs.registerOutParameter("p_cursor", OracleTypes.CURSOR);
ResultSet rs = (ResultSet) cs.getObject("p_cursor");
提升可讀性,避免索引錯誤。
String sql = "{call batch_insert(?, ?)}";
try (CallableStatement cs = conn.prepareCall(sql)) {
for (int i = 1; i <= 1000; i++) {
cs.setInt(1, i);
cs.setString(2, "Name_" + i);
cs.addBatch();
}
cs.executeBatch(); // 單次網路往返完成 1000 次呼叫
}
DatabaseMetaData meta = conn.getMetaData();
try (ResultSet rs = meta.getProcedures(null, "SCOTT", "SP_%")) {
while (rs.next()) {
System.out.println(rs.getString("PROCEDURE_NAME"));
}
}
建議:將預存程序呼叫封裝為 DAO 層方法,統一異常轉換、日誌記錄與資源管理,上層業務邏輯保持簡潔。