首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 數據庫知識 >  >> Oracle 資料庫 >> Oracle 資料庫基礎

JDBC 呼叫 Oracle 存儲過程:REF CURSOR 結果集與輸出參數雙重獲取實戰

     編輯:Oracle 資料庫基礎

前言

Oracle 存儲過程常需同時返回結果集 (REF CURSOR)與純量輸出參數 (計數、狀態碼等)。JDBC 呼叫時需正確註冊 OracleTypes.CURSOR 與對應型別,並按順序提取。本文提供完整 Spring + C3P0 + CallableStatement 範例,並解析 Package 定義、參數綁定順序、資源釋放等關鍵細節。


一、資料庫端:Package 定義與實作

1.1 Package Spec


CREATE OR REPLACE PACKAGE package_user AS
    TYPE my_resultset_cursor IS REF CURSOR;
    PROCEDURE person_query (
        rs OUT my_resultset_cursor,  -- 結果集
        c  OUT INTEGER               -- 統計計數
    );
END package_user;
/

1.2 Package Body


CREATE OR REPLACE PACKAGE BODY package_user AS
    PROCEDURE person_query (rs OUT my_resultset_cursor, c OUT INTEGER) AS
    BEGIN
        -- 1. 開啟游標返回查詢結果
        OPEN rs FOR SELECT empno, ename FROM emp;

        -- 2. 執行某業務邏輯 (示範 UPDATE)
        UPDATE some_table SET col1 = 'AAA' WHERE ...;

        -- 3. 計算輸出參數
        SELECT COUNT(*) INTO c FROM emp;
    END person_query;
END package_user;
/

二、Java 端:完整呼叫程式碼

2.1 依賴 (Maven)



    com.oracle.database.jdbc
    ojdbc8
    21.9.0.0


    org.springframework
    spring-jdbc
    5.3.x


    com.mchange
    c3p0
    0.9.5.5

2.2 Spring 配置 (applicationContext.xml)



    
    
    
    
    
    

2.3 呼叫類


package com.example.demo;

import java.sql.*;
import javax.sql.DataSource;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import oracle.jdbc.OracleTypes;

public class JdbcRefCursorDemo {
    public static void main(String[] args) throws Exception {
        ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
        DataSource ds = (DataSource) ctx.getBean("dataSource");

        Connection conn = null;
        CallableStatement cstmt = null;
        ResultSet rs = null;

        try {
            conn = ds.getConnection();
            // 呼叫語法: {call package.proc(?, ?)}  參數順序必須與過程定義一致
            cstmt = conn.prepareCall("{ CALL package_user.person_query(?, ?) }");

            // 1. 註冊輸出參數型別 (順序與 CALL 句法 ? 位置對應)
            cstmt.registerOutParameter(1, OracleTypes.CURSOR);  // REF CURSOR
            cstmt.registerOutParameter(2, OracleTypes.INTEGER); -- 計數

            // 2. 執行
            cstmt.execute();

            // 3. 取得 REF CURSOR (轉型為 ResultSet)
            rs = (ResultSet) cstmt.getObject(1);

            // 4. 遍歷結果集
            while (rs.next()) {
                System.out.println(rs.getInt(1) + "\t" + rs.getString(2));
            }

            // 5. 取得純量輸出參數
            int count = cstmt.getInt(2);
            System.err.println("Total count: " + count);

        } finally {
            // 6. 逆序關閉資源
            closeQuietly(rs);
            closeQuietly(cstmt);
            closeQuietly(conn);
        }
    }

    private static void closeQuietly(AutoCloseable ac) {
        if (ac != null) try { ac.close(); } catch (Exception ignore) {}
    }
}

三、關鍵技術點深度解析

環節關鍵點錯誤示範與後果
REF CURSOR 註冊registerOutParameter(idx, OracleTypes.CURSOR)用 Types.OTHER 或 Types.REF_CURSOR (JDBC 4.0+) 可能在舊驅動失效
參數順序嚴格對應 {call pkg.proc(?, ?)} 中 ? 位置順序顛倒 → ClassCastException 或數據錯誤
結果集提取cstmt.getObject(idx) 強轉 ResultSet直接 cstmt.getResultSet() 僅適用單結果集且為第一參數
純量輸出讀取cstmt.getInt/String/BigDecimal(idx)在 ResultSet 關閉前讀取較安全,部分驅動關閉 RS 會清空輸出參數
資源關閉順序ResultSet → CallableStatement → Connection先關 Connection 會導致隱式關閉 Statement/RS,但最顯式控制

四、進階場景與優化

4.1 多個 REF CURSOR 輸出


PROCEDURE multi_cursor (rs1 OUT SYS_REFCURSOR, rs2 OUT SYS_REFCURSOR, cnt OUT NUMBER);
-- Java
cstmt.registerOutParameter(1, OracleTypes.CURSOR);
cstmt.registerOutParameter(2, OracleTypes.CURSOR);
cstmt.registerOutParameter(3, OracleTypes.NUMBER);
cstmt.execute();
ResultSet rs1 = (ResultSet) cstmt.getObject(1);
ResultSet rs2 = (ResultSet) cstmt.getObject(2);
// 分別遍歷

4.2 使用 Spring SimpleJdbcCall 簡化


SimpleJdbcCall call = new SimpleJdbcCall(ds)
    .withCatalogName("PACKAGE_USER")
    .withProcedureName("PERSON_QUERY")
    .declareParameters(
        new SqlOutParameter("RS", OracleTypes.CURSOR, new RowMapper() { ... }),
        new SqlOutParameter("C", Types.INTEGER)
    );
Map result = call.execute();
List> rows = (List) result.get("RS");
Integer count = (Integer) result.get("C");

4.3 大結果集流式處理

  • 設定 cstmt.setFetchSize(1000) 減少網路往返。
  • 避免一次性 List.addAll 導致 OOM,邊讀邊處理/寫入下游。

五、常見異常排查表

異常原因解決
java.sql.SQLException: Invalid column type: 2012OracleTypes.CURSOR 常數值 -10 (2012) 驅動版本不匹配升級 ojdbc 至與 DB 版本相容版本
ORA-01001: invalid cursorResultSet 已關閉或未正確開啟檢查存儲過程 OPEN CURSOR 邏輯、確保未提前關閉
ClassCastException: oracle.jdbc.driver.T4CResultSetAccessorgetObject 返回型別非預期確認 registerOutParameter 使用 OracleTypes.CURSOR
數據庫會話洩漏異常路徑未關閉連線統一使用 try-with-resources 或 finally 關閉

六、總結

JDBC 呼叫 Oracle 存儲過程獲取 REF CURSOR + 輸出參數,核心在於:Package 定義強型別 REF CURSOR → CallableStatement 按位置註冊 OracleTypes.CURSOR 與純量型別 → execute() → getObject 轉 ResultSet → 讀取純量輸出 → 逆序關閉資源。採用 Spring SimpleJdbcCall 可進一步簡化樣板碼,提升可維護性。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved