首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 程式設計入門

Java 調用 Oracle 分頁存儲過程:PageInfo 參數封裝與 CallableStatement 完整實戰

     編輯:Java 程式設計入門

專案背景

在傳統 Java EE 專案中,常將分頁邏輯下推至資料庫存儲過程,由 Oracle 負責查詢總筆數、計算總頁數、返回當頁游標。本範例展示如何封裝分頁參數物件 PageInfo,配合 Hibernate Session 獲取 JDBC Connection,呼叫 CallableStatement 執行存儲過程 pages.prc_page,並將 REF CURSOR 轉換為 List<Map> 返回。

1. 分頁參數實體類

package org.zh.basic;

public class PageInfo {
    private String p_tableName;      // 表名
    private String p_strWhere;       // WHERE 條件(不含 WHERE 關鍵字)
    private String p_orderColumn;    // 排序欄位
    private String p_orderStyle;     // ASC/DESC
    private int p_curPage;           // 當前頁(1-based)
    private int p_pageSize;          // 每頁筆數
    private int p_totalRecords;      // 總筆數(OUT)
    private int p_totalPages;        // 總頁數(OUT)

    // 建構子、Getter/Setter 略...
}

2. 存儲過程呼叫實現

package org.zh.sys.server;

import java.sql.*;
import java.util.*;
import org.hibernate.Session;
import org.zh.basic.PageInfo;
import org.zh.dao.HibernateSessionFactory;
import oracle.jdbc.OracleTypes;
import oracle.jdbc.OracleCallableStatement;

public class GeneratePage {
    public static ArrayList<Map<String, Object>> Prc_Page(PageInfo page) {
        ArrayList<Map<String, Object>> list = new ArrayList<>();
        Session session = null;
        Connection conn = null;
        ResultSet rs = null;
        CallableStatement proc = null;

        try {
            // 1. 從 Hibernate 獲取原生 Connection
            session = HibernateSessionFactory.getSession();
            conn = session.connection(); // 注意:Hibernate 5+ 建議用 session.doWork()

            // 2. 準備呼叫:{call pages.prc_page(?,?,?,?,?,?,?,?,?)}
            proc = conn.prepareCall("{call pages.prc_page(?,?,?,?,?,?,?,?,?)}");
            proc.setString(1, page.getP_tableName());
            proc.setString(2, page.getP_strWhere());
            proc.setString(3, page.getP_orderColumn());
            proc.setString(4, page.getP_orderStyle());
            proc.setInt(5, page.getP_curPage());
            proc.setInt(6, page.getP_pageSize());

            // 3. 註冊 OUT 參數:總筆數、總頁數、游標
            proc.registerOutParameter(7, OracleTypes.NUMBER);
            proc.registerOutParameter(8, OracleTypes.NUMBER);
            proc.registerOutParameter(9, OracleTypes.CURSOR);

            // 4. 執行
            proc.execute();

            // 5. 取回 OUT 參數
            page.setP_totalRecords(proc.getInt(7));
            page.setP_totalPages(proc.getInt(8));

            // 6. 取回 REF CURSOR 並轉換
            rs = ((OracleCallableStatement) proc).getCursor(9);
            ResultSetMetaData rsmd = rs.getMetaData();
            int cols = rsmd.getColumnCount();

            while (rs.next()) {
                Map<String, Object> row = new HashMap<>(cols);
                for (int i = 1; i <= cols; i++) {
                    row.put(rsmd.getColumnName(i), rs.getObject(i));
                }
                list.add(row);
            }
            return list;

        } catch (SQLException | Exception e) {
            e.printStackTrace();
            return list;
        } finally {
            // 7. 資源關閉順序:ResultSet → CallableStatement → Connection
            closeQuietly(rs);
            closeQuietly(proc);
            closeQuietly(conn);
            // 注意:Session 由外層管理,此處不關閉
        }
    }

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

Oracle 存儲過程簽名參考

CREATE OR REPLACE PACKAGE pages AS
  PROCEDURE prc_page(
    p_tableName    IN  VARCHAR2,
    p_strWhere     IN  VARCHAR2,
    p_orderColumn  IN  VARCHAR2,
    p_orderStyle   IN  VARCHAR2,
    p_curPage      IN  NUMBER,
    p_pageSize     IN  NUMBER,
    p_totalRecords OUT NUMBER,
    p_totalPages   OUT NUMBER,
    v_cur          OUT SYS_REFCURSOR
  );
END pages;
/
-- 過程體內部動態構建 SQL,執行 COUNT(*) 取總數,再用 ROWNUM/BETWEEN 或 OFFSET/FETCH 分頁

關鍵技術點解析

環節說明注意事項
Connection 來源Hibernate session.connection()Hibernate 5.3+ 棄用,建議 session.doWork(c -> ...) 或 session.unwrap(Session.class).connection()
CallableStatementJDBC 標準呼叫存儲過程介面使用 {call ...} 轉義語法,跨資料庫可攜性較好
OUT 參數註冊registerOutParameter(idx, OracleTypes.NUMBER/CURSOR)必須使用 OracleTypes 常數,CURSOR 對應 SYS_REFCURSOR
REF CURSOR 獲取((OracleCallableStatement)proc).getCursor(idx)需強轉 Oracle 實作類,綁定 Oracle 驅動
ResultSet 轉 MapResultSetMetaData 取欄位名 + getObject通用但失去型別資訊,實際專案建議映射到 DTO/Entity
資源釋放finally 區塊依序關閉使用 try-with-resources (JDBC 4.1+) 更簡潔

現代化改進建議

  1. Spring JdbcTemplate:SimpleJdbcCall 自動處理 OUT 參數、RowMapper 映射實體,程式碼量減少 80%。
  2. MyBatis:<select resultMap="..." statementType="CALLABLE">{call prc_page(...)}</select> 配合 resultType="map" 或自定義 ResultHandler。
  3. Hibernate 5+ 原生查詢:session.createNativeQuery("CALL prc_page(...)").setParameter(...).list()。
  4. 分頁標準化:Oracle 12c+ 支援 OFFSET ... ROWS FETCH NEXT ... ROWS ONLY,可直接用 SQL 分頁,無需存儲過程。
  5. 連線池:確保 HibernateSessionFactory 整合 HikariCP/Druid,避免頻繁建立物理連線。

總結

本範例完整演示了「參數物件 → Hibernate Session → JDBC Connection → CallableStatement → REF CURSOR → List<Map>」的經典調用鏈路。雖現代框架已大幅簡化此流程,但理解底層 JDBC 機制對排查效能問題、處理複雜存儲過程、跨框架整合仍具核心價值。

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