Oracle PL/SQL 包设计与开发

Oracle PL/SQL 包设计与开发

适用版本:Oracle Database 10g / 11g / 12c / 19c / 23ai 文档版本:v1.0 / 2026-07


1. 概述

包是 PL/SQL 模块化单元[1]:

组成

  • 包规范(Spec):声明
  • 包体(Body):实现

详细见:Oracle PL/SQL 包设计


2. 包规范

CREATE OR REPLACE PACKAGE emp_pkg AS
  -- 公共类型
  TYPE emp_rec IS RECORD (
    id employees.id%TYPE,
    name employees.name%TYPE,
    salary employees.salary%TYPE
  );
  
  -- 公共常量
  c_min_salary CONSTANT NUMBER := 5000;
  
  -- 公共变量
  g_dept_id NUMBER;
  
  -- 公共异常
  e_low_salary EXCEPTION;
  
  -- 公共游标
  CURSOR c_emp RETURN emp_rec;
  
  -- 公共过程
  PROCEDURE hire_emp(p_name VARCHAR2, p_salary NUMBER);
  PROCEDURE fire_emp(p_id NUMBER);
  
  -- 公共函数
  FUNCTION get_emp(p_id NUMBER) RETURN emp_rec;
  FUNCTION get_count RETURN NUMBER;
END emp_pkg;
/

3. 包体

CREATE OR REPLACE PACKAGE BODY emp_pkg AS
  -- 私有变量
  v_total_hired NUMBER := 0;
  
  -- 私有函数
  FUNCTION is_valid_salary(p_salary NUMBER) RETURN BOOLEAN IS
  BEGIN
    RETURN p_salary >= c_min_salary;
  END;
  
  -- 公共游标实现
  CURSOR c_emp RETURN emp_rec IS
    SELECT id, name, salary FROM employees;
  
  -- 公共过程实现
  PROCEDURE hire_emp(p_name VARCHAR2, p_salary NUMBER) IS
    v_id NUMBER;
  BEGIN
    IF NOT is_valid_salary(p_salary) THEN
      RAISE e_low_salary;
    END IF;
    
    INSERT INTO employees (id, name, salary) 
    VALUES (emp_seq.NEXTVAL, p_name, p_salary)
    RETURNING id INTO v_id;
    
    v_total_hired := v_total_hired + 1;
  END;
  
  PROCEDURE fire_emp(p_id NUMBER) IS
  BEGIN
    DELETE FROM employees WHERE id = p_id;
    IF SQL%ROWCOUNT = 0 THEN
      RAISE_APPLICATION_ERROR(-20001, 'Employee not found');
    END IF;
  END;
  
  -- 公共函数实现
  FUNCTION get_emp(p_id NUMBER) RETURN emp_rec IS
    v_rec emp_rec;
  BEGIN
    SELECT id, name, salary INTO v_rec
    FROM employees WHERE id = p_id;
    RETURN v_rec;
  EXCEPTION
    WHEN NO_DATA_FOUND THEN
      RETURN NULL;
  END;
  
  FUNCTION get_count RETURN NUMBER IS
    v_count NUMBER;
  BEGIN
    SELECT COUNT(*) INTO v_count FROM employees;
    RETURN v_count;
  END;
  
  -- 初始化块(可选)
BEGIN
  SELECT dept_id INTO g_dept_id FROM ... WHERE ...;
END emp_pkg;
/

4. 包调用

4.1 过程

EXEC emp_pkg.hire_emp('Alice', 8000);
EXEC emp_pkg.fire_emp(100);

4.2 函数

DECLARE
  v_rec emp_pkg.emp_rec;
BEGIN
  v_rec := emp_pkg.get_emp(100);
  DBMS_OUTPUT.PUT_LINE(v_rec.name);
END;
/

SELECT emp_pkg.get_count FROM dual;

4.3 公共变量

BEGIN
  emp_pkg.g_dept_id := 10;
END;
/

5. 重载

CREATE OR REPLACE PACKAGE calc_pkg AS
  FUNCTION add(a NUMBER, b NUMBER) RETURN NUMBER;
  FUNCTION add(a VARCHAR2, b VARCHAR2) RETURN VARCHAR2;
  FUNCTION add(a DATE, b NUMBER) RETURN DATE;
END;
/

CREATE OR REPLACE PACKAGE BODY calc_pkg AS
  FUNCTION add(a NUMBER, b NUMBER) RETURN NUMBER IS
  BEGIN
    RETURN a + b;
  END;
  
  FUNCTION add(a VARCHAR2, b VARCHAR2) RETURN VARCHAR2 IS
  BEGIN
    RETURN a || b;
  END;
  
  FUNCTION add(a DATE, b NUMBER) RETURN DATE IS
  BEGIN
    RETURN a + b;
  END;
END;
/

6. 包状态

6.1 持久状态

- 公共变量在会话内持久
- UGA 存储
- 会话结束清理

6.2 重置

EXEC DBMS_SESSION.MODIFY_PACKAGE_STATE(DBMS_SESSION.REINITIALIZE);

6.3 SERIALLY_REUSABLE

CREATE OR REPLACE PACKAGE emp_pkg IS
  PRAGMA SERIALLY_REUSABLE;
  ...
END;
/

-- 状态不持久,每次调用后释放

7. 包编译

7.1 编译

ALTER PACKAGE emp_pkg COMPILE;
ALTER PACKAGE emp_pkg COMPILE SPECIFICATION;
ALTER PACKAGE emp_pkg COMPILE BODY;

7.2 错误

SHOW ERRORS PACKAGE emp_pkg;
SHOW ERRORS PACKAGE BODY emp_pkg;

SELECT * FROM user_errors WHERE name = 'EMP_PKG';

8. 包依赖

8.1 查看

SELECT name, type, referenced_name, referenced_type
FROM user_dependencies
WHERE name = 'EMP_PKG';

8.2 失效

- 依赖对象修改
- 包自动失效
- 下次调用重新编译

9. 包权限

GRANT EXECUTE ON emp_pkg TO scott;
GRANT EXECUTE ON emp_pkg TO public;

-- 调用
EXEC scott.emp_pkg.hire_emp(...);

10. 包信息

SELECT object_name, object_type, status 
FROM user_objects 
WHERE object_type LIKE 'PACKAGE%';

SELECT text FROM user_source 
WHERE name = 'EMP_PKG' 
ORDER BY line;

11. AUTHID

11.1 DEFINER(默认)

CREATE OR REPLACE PACKAGE emp_pkg AUTHID DEFINER AS
  ...
END;
/
-- 以包所有者权限执行

11.2 CURRENT_USER

CREATE OR REPLACE PACKAGE emp_pkg AUTHID CURRENT_USER AS
  ...
END;
/
-- 以调用者权限执行

12. 包设计原则

12.1 模块化

- 相关功能聚集
- 单一职责
- 清晰接口

12.2 封装

- 私有变量/过程
- 公共接口稳定
- 实现可变

12.3 命名

- 包名:模块名_pkg
- 过程:动词_名词
- 函数:get_/is_/has_

13. 常用包

13.1 DBMS_OUTPUT

DBMS_OUTPUT.PUT_LINE('Hello');
DBMS_OUTPUT.ENABLE(1000000);

13.2 DBMS_STATS

DBMS_STATS.GATHER_TABLE_STATS(...);

13.3 DBMS_SCHEDULER

DBMS_SCHEDULER.CREATE_JOB(...);

13.4 UTL_FILE

UTL_FILE.FOPEN(...);
UTL_FILE.PUT_LINE(...);

13.5 UTL_MAIL

UTL_MAIL.SEND(...);

14. 常见坑与排错

14.1 状态丢失

- 包重编译
- 公共变量重置
- 注意依赖

14.2 ORA-04068

- 包被修改
- 调用方需重新连接

14.3 循环依赖

- 包 A 依赖包 B
- 包 B 依赖包 A
- 重构分离

15. 最佳实践

  1. 规范 + 体分离:清晰
  2. 公共/私有分离:封装
  3. 重载便利:多态
  4. 避免大量公共变量:状态
  5. SERIALLY_REUSABLE:内存
  6. AUTHID 选择:权限
  7. 异常处理:完整
  8. 权限授予:控制
  9. 文档化:接口
  10. 测试:质量

16. 参考资料

[1] Oracle Database PL/SQL Language Reference 19c, “Packages” https://docs.oracle.com/en/database/oracle/oracle-database/19/lnpls/packages.html