核心思想:**用戶無表直接權限,僅通過存儲過程訪問數據**。存儲過程以定義者權限 (AUTHID DEFINER) 運行,擁有表操作權限,實現細粒度控制:時間窗口限制、列級掩碼、審計日誌、業務規則強制執行。
IF TO_CHAR(SYSDATE,'HH24') NOT BETWEEN '09' AND '18' THEN RAISE_APPLICATION_ERROR(-20001,'非工作時間禁止更新'); END IF;CASE WHEN v$session.user = 'HR' THEN MASK(salary) ELSE salary END)。audit_log(who, when, sql_text, bind_vals),不可繞過。Legacy (Forms/Reports) 與 J2EE (EJB/Spring) 同時調用同一組 PL/SQL/Java 存儲過程,Call Spec 屏蔽實現語言差異:
-- 同一邏輯,PL/SQL 與 Java 互為備選
CREATE OR REPLACE PROCEDURE calc_bonus(p_empno NUMBER, p_bonus OUT NUMBER)
AS LANGUAGE JAVA NAME 'BonusCalc.calc(int, int[])'; -- Java 實現
-- 或
CREATE OR REPLACE PROCEDURE calc_bonus(p_empno NUMBER, p_bonus OUT NUMBER) IS ... -- PL/SQL 實現
-- 調用端無感知:{call calc_bonus(?, ?)}
場景:ejbCreate() 需返回主鍵,主鍵由數據庫序列/邏輯生成。
-- Java 存儲過程:插入數據 + 獲取序列 + 返回主鍵
public class GenPK {
public static BigDecimal insertAccount(String owner, BigDecimal bal) throws SQLException {
try (Connection conn = DriverManager.getConnection("jdbc:default:connection:");
CallableStatement cs = conn.prepareCall("{? = call seq_account.NEXTVAL}")) {
cs.registerOutParameter(1, Types.NUMERIC);
cs.execute();
BigDecimal newId = cs.getBigDecimal(1);
try (PreparedStatement ps = conn.prepareStatement(
"INSERT INTO accounts (id, owner, balance) VALUES (?, ?, ?)")) {
ps.setBigDecimal(1, newId);
ps.setString(2, owner);
ps.setBigDecimal(3, bal);
ps.executeUpdate();
}
return newId;
}
}
}
-- Call Spec
CREATE OR REPLACE FUNCTION insert_account(p_owner VARCHAR2, p_bal NUMBER) RETURN NUMBER
AS LANGUAGE JAVA NAME 'GenPK.insertAccount(java.lang.String, java.math.BigDecimal) return java.math.BigDecimal';
/
-- EJB ejbCreate 實現
public AccountPK ejbCreate(String ownerName, int balance) throws CreateException {
try (Connection conn = dataSource.getConnection();
CallableStatement cs = conn.prepareCall("{? = call insert_account(?, ?)}")) {
cs.registerOutParameter(1, Types.NUMERIC);
cs.setString(2, ownerName);
cs.setBigDecimal(3, new BigDecimal(balance));
cs.execute();
return new AccountPK(cs.getBigDecimal(1));
} catch (SQLException e) {
throw new CreateException(e.getMessage());
}
}
PreparedStatement ps = conn.prepareStatement(
"INSERT INTO accounts (owner, balance) VALUES (?, ?)",
Statement.RETURN_GENERATED_KEYS); // 或 new String[]{"ID"}
ps.setString(1, owner);
ps.setBigDecimal(2, bal);
ps.executeUpdate();
try (ResultSet rs = ps.getGeneratedKeys()) {
if (rs.next()) return new AccountPK(rs.getBigDecimal(1));
}
對比:JDBC 方案可移植性更強 (跨數據庫);存儲過程方案性能更高 (單次往返、邏輯封裝)、支持複雜主鍵邏輯 (組合鍵、分表路由)。
| 項目 | 要求 |
|---|---|
| 權限模型 | 表授權給存儲過程擁有者,用戶僅授予 EXECUTE ON PROCEDURE |
| 參數化查詢 | 存儲過程內部強制使用綁定變量,杜絕 SQL 注入 |
| 異常處理 | 業務異常 RAISE_APPLICATION_ERROR(-20xxx, 'msg'),系統異常記錄日志後拋出 |
| 審計完整性 | 審計表唯追加、不可刪改、定期歸檔、備份異地 |
| 版本管理 | 存儲過程代碼納入 Git,部署走 CI/CD,保留回滾腳本 |
將存儲過程作為數據訪問網關,是 Oracle 實現「最小權限原則」「業務邏輯集中化」「異構應用解耦」的經典架構。配合 Java 存儲過程處理複雜計算/外部調用,PL/SQL 處理事務編排/數據流轉,構建安全、高性能、可維護的數據服務層。