海量數據查詢場景下,分頁是必須解決的問題。本文展示 Java 調用 Oracle 存儲過程實現高效分頁的完整方案,包含分頁參數封裝類 PageInfo、Hibernate 獲取原生連線調用 CallableStatement、結果集映射為 List
標準化分頁參數傳遞,包含表名、查詢條件、排序字段、排序方式、當前頁、頁大小、總記錄數、總頁數等完整屬性,提供全參構造器與無參構造器,符合 JavaBean 規範。
public class PageInfo {
private String p_tableName; // 表名
private String p_strWhere; // 查詢條件
private String p_orderColumn; // 排序列
private String p_orderStyle; // 排序方式 (ASC/DESC)
private int p_curPage; // 當前頁
private int p_pageSize; // 頁大小
private int p_totalRecords; // 總記錄數 (輸出參數)
private int p_totalPages; // 總頁數 (輸出參數)
// 無參構造器
public PageInfo() {}
// 全參構造器
public PageInfo(String p_tableName, String p_strWhere,
String p_orderColumn, String p_orderStyle, int p_curPage,
int p_pageSize, int p_totalRecords, int p_totalPages) {
this.p_tableName = p_tableName;
this.p_strWhere = p_strWhere;
this.p_orderColumn = p_orderColumn;
this.p_orderStyle = p_orderStyle;
this.p_curPage = p_curPage;
this.p_pageSize = p_pageSize;
this.p_totalRecords = p_totalRecords;
this.p_totalPages = p_totalPages;
}
// Getter/Setter 省略...
}
使用 Hibernate Session 獲取底層 JDBC Connection,準備 CallableStatement 調用 Oracle 存儲過程 pages.prc_page。關鍵點:註冊輸出參數類型 (OracleTypes.NUMBER, OracleTypes.CURSOR)、獲取 REF CURSOR 並轉換為 List
public class GeneratePage {
public static ArrayList Prc_Page(PageInfo page) {
ArrayList list = new ArrayList();
Map mp;
Session s = null;
Connection conn = null;
ResultSet rs = null;
CallableStatement proc = null;
try {
s = HibernateSessionFactory.getSession();
conn = s.connection();
// 調用存儲過程:9個參數 (7輸入 + 2輸出 + 1游標輸出)
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());
// 註冊輸出參數
proc.registerOutParameter(7, OracleTypes.NUMBER); // 總記錄數
proc.registerOutParameter(8, OracleTypes.NUMBER); // 總頁數
proc.registerOutParameter(9, OracleTypes.CURSOR); // 結果游標
proc.execute();
// 獲取輸出參數回填 PageInfo
page.setP_totalRecords(proc.getInt(7));
page.setP_totalPages(proc.getInt(8));
// 獲取 REF CURSOR 並轉換
rs = ((OracleCallableStatement) proc).getCursor(9);
ResultSetMetaData rsmd = rs.getMetaData();
int columnCount = rsmd.getColumnCount();
while (rs.next()) {
mp = new HashMap(columnCount);
for (int r = 1; r <= columnCount; r++) {
mp.put(rsmd.getColumnName(r), rs.getObject(r));
}
list.add(mp);
}
return list;
} catch (SQLException ex) {
ex.printStackTrace();
return list;
} catch (Exception ex2) {
ex2.printStackTrace();
return list;
} finally {
// 資源釋放順序:Statement -> ResultSet -> Connection
try {
if (proc != null) proc.close();
if (rs != null) rs.close();
if (conn != null) conn.close();
} catch (SQLException ex1) {
ex1.printStackTrace();
}
}
}
}
| 參數位置 | 參數名 | 類型 | 模式 | 說明 |
|---|---|---|---|---|
| 1 | p_tableName | VARCHAR2 | IN | 查詢表名 |
| 2 | p_strWhere | VARCHAR2 | IN | WHERE 條件子句 (不含 WHERE 關鍵字) |
| 3 | p_orderColumn | VARCHAR2 | IN | 排序字段名 |
| 4 | p_orderStyle | VARCHAR2 | IN | 排序方向 ASC/DESC |
| 5 | p_curPage | NUMBER | IN | 當前頁碼 (從 1 開始) |
| 6 | p_pageSize | NUMBER | IN | 每頁記錄數 |
| 7 | p_totalRecords | NUMBER | OUT | 返回總記錄數 |
| 8 | p_totalPages | NUMBER | OUT | 返回總頁數 |
| 9 | v_cur | REF CURSOR | OUT | 返回分頁結果集游標 |
| 異常現象 | 可能原因 | 解決方案 |
|---|---|---|
| Invalid column type | registerOutParameter 類型不匹配 | 確認 OracleTypes 常量與存儲過程定義一致 |
| Closed Connection | 連線池回收導致連線失效 | 檢查連線池驗證查詢、idleTimeout 配置 |
| Cursor already closed | ResultSet 使用前連線已關閉 | 確保在 finally 關閉連線前處理完游標數據 |
| ORA-06550 | 存儲過程不存在/參數錯誤 | 驗證存儲過程名稱、參數個數、類型、順序 |