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

Oracle 集合類型作為存儲過程參數:PL/SQL 與 Java JDBC 完整實戰

     編輯:Oracle 資料庫基礎

前言

Oracle 無原生 Array 型別,但提供 Collection(集合)類型:VARRAY(定長陣列)、Nested Table(巢狀表)、Associative Array(關聯陣列,僅 PL/SQL 可用)。其中 Nested Table 可儲存於資料庫、作為 SQL/存儲過程參數,是模擬 Array 傳遞的標準方案。本文示範:建立 Nested Table 型別 → 存儲過程接收並遍歷 → Java JDBC 使用 oracle.sql.ARRAY / ArrayDescriptor 傳遞 List 資料。


一、資料庫端:型別與存儲過程

1.1 建立 Nested Table 型別

CREATE OR REPLACE TYPE varchar_array IS TABLE OF VARCHAR2(128);
/
  • IS TABLE OF 宣告為 Nested Table(無長度上限,稀疏儲存)。
  • 型別建立於 Schema 層級,可被 SQL、PL/SQL、JDBC 共同引用。

1.2 目標表

CREATE TABLE tb (col1 VARCHAR2(128));

1.3 存儲過程:接收集合並批次插入

CREATE OR REPLACE PROCEDURE test1 (arr IN varchar_array) IS
BEGIN
    FOR i IN arr.FIRST .. arr.LAST LOOP
        -- Nested Table 可能稀疏,建議用 FIRST/LAST 配合 EXISTS 判斷
        IF arr.EXISTS(i) THEN
            INSERT INTO tb VALUES (arr(i));
        END IF;
    END LOOP;
    -- 視業務決定是否在此 COMMIT,通常由呼叫端控制交易邊界
END test1;
/

二、Java JDBC 端:完整呼叫範例

2.1 依賴

  • Oracle JDBC 驅動:ojdbc8.jar (JDK 8+) 或 ojdbc11.jar (JDK 11+),需與資料庫版本相容。
  • Maven:com.oracle.database.jdbc:ojdbc8:21.9.0.0 (從 Oracle Maven Repo 下載)。

2.2 核心程式碼


import java.sql.*;
import java.util.ArrayList;
import java.util.List;
import oracle.jdbc.OracleConnection;
import oracle.sql.ARRAY;
import oracle.sql.ArrayDescriptor;

public class ArrayProcDemo {
    public static void main(String[] args) {
        String url = "jdbc:oracle:thin:@192.168.1.199:1521:orcl";
        String user = "scott";
        String password = "tiger";

        Connection conn = null;
        CallableStatement cstmt = null;

        try {
            // 1. 取得連線
            conn = DriverManager.getConnection(url, user, password);
            conn.setAutoCommit(false);  // 交易由應用控制

            // 2. 準備資料
            List list = new ArrayList<>();
            list.add("A");
            list.add("B");
            list.add("C");

            // 3. 取得 ArrayDescriptor (型別名稱區分大小寫,需與 DB 端一致)
            ArrayDescriptor descriptor = ArrayDescriptor.createDescriptor("VARCHAR_ARRAY", conn);

            // 4. 將 List 轉為 oracle.sql.ARRAY
            ARRAY oracleArray = new ARRAY(descriptor, conn, list.toArray());

            // 5. 呼叫存儲過程
            cstmt = conn.prepareCall("{ CALL test1(?) }");
            cstmt.setArray(1, oracleArray);
            cstmt.execute();

            // 6. 提交交易
            conn.commit();
            System.out.println("批次插入成功,筆數: " + list.size());

        } catch (SQLException e) {
            e.printStackTrace();
            try { if (conn != null) conn.rollback(); } catch (SQLException ex) { ex.printStackTrace(); }
        } finally {
            // 7. 資源關閉 (JDBC 4.0+ try-with-resources 更佳)
            closeQuietly(cstmt);
            closeQuietly(conn);
        }
    }

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

三、關鍵技術點深度解析

環節關鍵類/方法說明
型別描述符ArrayDescriptor.createDescriptor(typeName, conn)快取建議:型別描述符可重用,建議應用啟動時建立一次緩存
ARRAY 建構new ARRAY(descriptor, conn, javaArray)javaArray 為 Object[],元素型別需對應 SQL 型別
綁定參數cstmt.setArray(idx, oracleArray)對應 CALL 句法中 ? 位置
PL/SQL 遍歷FOR i IN arr.FIRST..arr.LAST + arr.EXISTS(i)Nested Table 稀疏特性,避免 NO_DATA_FOUND

3.1 型別名稱大小寫敏感

ArrayDescriptor.createDescriptor("VARCHAR_ARRAY", conn) 中的型別名稱必須與資料庫端 CREATE TYPE 定義的大小寫完全一致。Oracle 內部儲存為大寫,但建議統一用大寫避免困擾。

3.2 交易邊界控制

  • 範例中 conn.setAutoCommit(false) + 成功後 commit() / 失敗 rollback()。
  • 存儲過程內不建議自行 COMMIT,除非為自主交易 (PRAGMA AUTONOMOUS_TRANSACTION),否則破壞外層交易一致性。

3.3 批次效能優化

  1. FORALL 批次綁定:PL/SQL 端改用 FORALL i IN INDICES OF arr INSERT INTO tb VALUES (arr(i)),單次 SQL 引擎呼叫,效能指數級提升。
  2. JDBC 批次處理:若無存儲過程包裝,可直接用 PreparedStatement.addBatch() + executeBatch()。
  3. 大量資料:考慮分批 (如每 1000 筆)、暫存表 + INSERT /*+ APPEND */、或外部表/ SQL*Loader。

四、進階:支援 NUMBER、DATE 等非字串型別

4.1 定義數字型別集合

CREATE OR REPLACE TYPE number_array IS TABLE OF NUMBER;
/
CREATE OR REPLACE PROCEDURE test_num (arr IN number_array) IS
BEGIN
    FORALL i IN INDICES OF arr
        INSERT INTO num_tbl VALUES (arr(i));
END;
/

4.2 Java 端對應


List nums = Arrays.asList(new BigDecimal("1.1"), new BigDecimal("2.2"));
ArrayDescriptor numDesc = ArrayDescriptor.createDescriptor("NUMBER_ARRAY", conn);
ARRAY numArray = new ARRAY(numDesc, conn, nums.toArray());
// 後續 setArray/execute 同字串範例

五、常見錯誤與排查

錯誤現象原因解決
PLS-00306: wrong number or types of arguments型別名稱拼錯、大小寫不符、或未建立型別檢查 SELECT type_name FROM user_types 確認
ORA-21700: object does not exist or is marked for deleteArrayDescriptor 建立失敗,型別不存在同左
ClassCastException: cannot cast to oracle.sql.ARRAY使用非 Oracle 專用 Connection (如連線池包裝後)conn.unwrap(OracleConnection.class) 取得原生連線
NullPointerException 在 ARRAY 建構List 含 null 元素,但 SQL 型別 NOT NULL預先過濾 null 或調整表欄位允許 NULL

六、總結

Oracle Nested Table + JDBC oracle.sql.ARRAY 是實現「集合/陣列參數化」的標準路徑。核心步驟:DB 端建型別 → 存儲過程遍歷/批次綁定 → Java 端 ArrayDescriptor + ARRAY + setArray。生產環境務必加上交易控制、異常處理、資源關閉、型別描述符快取、FORALL 批次優化,才能穩定高效運行。

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