把相关的变量、常量、游标、异常和子程序等从逻辑上组织在一起形成一个包,包是模式下的一种对象。
一个包总是有一个包规范,它声明了可以从包外部引用的公共项。
如果包的公共项中包含游标或子程序,则包还必须有一个包体。包体必须定义公共游标的查询和公共子程序的实现。包体还可以声明和定义不能从包外部引用但对于包的内部工作来说是必需的私有项。包体可以有一个初始化部分,用于初始化变量、执行其他一次性逻辑并进行异常处理。您可以在不更改包规范或对包中公共项的引用的情况下更改包体;所以,您可以将包体视为一个黑匣子。
包规范的 AUTHID 子句决定包中的子程序和游标是以其定义者(默认)还是调用者的权限运行,以及包中对模式下其他对象的非限定引用是在定义者的模式中解析还是调用者的模式中解析。
另请参阅
"包规范",了解更多关于包规范的信息 "包体" 获取更多关于包体的信息 "函数的声明与定义" "过程的声明与定义" "调用者的权利和定义者的权利(AUTHID财产)"
包用于开发可靠的,可维护的,可重复使用的代码。
模块化
通过使用包可以把逻辑上相关的变量、函数等封装成为一个 PL/SQL 模块。这样的包更易于理解,且包与包之间的接口也简单明了,方便定义,这样非常有利于应用开发。
简化应用程序设计
当设计一个应用程序时,最初仅需要包规范的接口信息。您可以先编写包规范代码并进行编译,而暂不需要定义包体;包体可以在需要实际完成应用程序时再定义。
隐藏实现的逻辑
包可以让您在包规范中共享您的接口信息,并在包体中隐藏具体的实现逻辑。将实现逻辑隐藏在包体中具有以下优点:
您可以在不影响应用程序的情况下更改实现逻辑。
应用程序用户无法开发依赖于您可能想要更改的实现逻辑的代码。
功能增强
包中的公共变量和游标在一个会话中会一直存在,可以被当前会话中的其它函数、存储过程或者其他包共享。它们使您可以跨事务维护数据,而无需将其存储在数据库中。
高效性
第一次调用包函数或包存储过程时,整个包被加载到内存,以后再访问该包的对象时,不需要再进行 I/O 操作。
包可以防止级联依赖和不必要的重新编译。例如,如果您更改包体中的子函数的定义,数据库不会重新编译调用该函数的其他子程序,因为这些子程序仅依赖于包规范中的函数声明。
方便授权
对包的授权同时将包中所有的对象授权。
包规范负责声明公共项,公共项的可见域为包所在的模式,即包中公共项对包所在的模式中的任何对象都可见。要引用可见域内包中的公共项,需要使用包名称对其进行限定。(有关范围、可见性和限定的信息,请参阅 标识符的范围和可见性。)
每个公共项的声明都包含了使用该公共项所需的所有信息。例如,假设包规范以以下方式声明函数 func
FUNCTION func (n INTEGER) RETURN INTEGER; -- returns n
该声明表明函数 func 需要一个 INTEGER 类型参数并返回一个 INTEGER 类型的值。调用者必须知道应该使用什么值调用函数 func,而不需要知道 func 是如何实现的。
包中的公共项包括:
可被多个子程序使用的类型、变量、常量、子程序、游标和异常
包规范中定义的类型可以是 PL/SQL 用户定义的子类型(在 用户定义的 PL/SQL 子类型 中描述)或 PL/SQL 复合类型(在PL/SQL 集合和记录中描述)。
可用于独立子程序参数的关联数组类型
您不能在模式中定义关联数组类型。因此,需要要将关联数组变量作为参数传递给独立子程序时,您必须在包规范中声明关联数组类型。这样做可以使被调用的子程序(声明该类型的参数)和调用子程序或匿名块(声明该类型的变量)都可以使用该类型。见 示例2。
在同一会话中的子程序之间必须保持可用的变量
用于读写公共变量的子程序
提供这些子程序来防止包用户直接读写公共变量。
相互调用的子程序
您不必像关心相互调用的独立子程序一样考虑包中子程序的编译顺序。
重载的子程序
重载的子程序是同一子程序的变体。也就是说,它们具有相同的名称,但参数不同。有关它们的更多信息,请参阅 重载子程序 。
使用 CREATE PACKAGE语句 语句创建包规范。
如果包规范中未声明子程序,则该包可以不需要创建包体。
示例 1 简单的包规范
在这个例子中,包 pkg1 的规范声明了两个公共类型和三个公共变量以及一个公共异常。
\set SQLTERM / CREATE OR REPLACE PACKAGE pkg1 AUTHID DEFINER AS TYPE rec1 IS RECORD ( col1 INT, col2 TEXT); TYPE rec2 IS RECORD ( col1 VARCHAR2(10), col2 INT, col3 REAL); var1 CONSTANT REAL := 10.00; var2 INT; excep EXCEPTION; PRAGMA EXCEPTION_INIT(excep, -100); END pkg1; / \set SQLTERM ;
示例 2 将关联数组传递给独立子程序
在此示例中,包 pkg2 的规范中声明了关联数组类型 typ1 然后,独立存储过程 proc1 声明了一个 typ1 类型的参数。 接下来,匿名块声明一个 typ1 类型的变量为其赋值,并将它传递给打印它的存储过程 proc1。
set serveroutput on \set SQLTERM / CREATE OR REPLACE PACKAGE pkg2 AUTHID DEFINER IS TYPE typ1 IS TABLE OF INTEGER INDEX BY VARCHAR2(15); END; / CREATE OR REPLACE PROCEDURE proc1 ( p1 pkg2.typ1 ) AUTHID DEFINER IS i VARCHAR2(15); BEGIN i := p1.FIRST; WHILE i IS NOT NULL LOOP DBMS_OUTPUT.PUT_LINE (p1(i) || ' ' || i); i := p1.NEXT(i); END LOOP; END; / \set SQLTERM ; \set SQLTERM / DECLARE v1 pkg2.typ1; BEGIN v1('zero') := 0; v1('one') := 1; v1('two') := 2; proc1(v1); END; / \set SQLTERM ;
结果:
1 one 2 two 0 zero
如果包规范声明了子程序,则必须要创建包体;否则,包体是可选的。包体和包规范必须在同一个模式中。
包规范中的每个子程序声明都必须在包体中具有相应的定义。相应子程序的声明和定义必须匹配,空格除外。
使用 CREATE PACKAGE BODY语句 语句创建包体。
在包规范中声明并在包体中定义的子程序是可以从包外部引用的公共项。包体还可以声明和定义不能从包外部引用但可用于包的内部的私有项。
包体还可以有一个初始化部分,用于初始化公共变量并执行一些一次性逻辑。初始化部分仅在第一次引用包时运行。初始化部分可以包括异常处理程序。
您可以在未更改包规范中公共项的情况下更改包体。
示例 3 匹配包规范和包体
在 示例3 中,相应的子程序声明和定义的参数名不匹配;因此,PL/SQL 会引发异常.
DROP TABLE IF EXISTS student CASCADE; CREATE TABLE student (id INT PRIMARY KEY, name TEXT, score NUMBER); \set SQLTERM / CREATE PACKAGE pkg3 AS PROCEDURE proc1 (p1 student.name%TYPE); END pkg3; / CREATE PACKAGE BODY pkg3 AS -- 参数名 p2 与声明的参数名 p1 不一致 PROCEDURE proc1 (p2 student.name%TYPE) IS BEGIN DBMS_OUTPUT.PUT_LINE('Student name is ' || p1); END; END pkg3; / \set SQLTERM ;
结果:
ERROR: procedure "proc1" is declared in package "pkg3" and must be defined in the package body
正确示例:
\set SQLTERM / CREATE PACKAGE BODY pkg3 AS PROCEDURE proc1 (p1 student.name%TYPE) IS BEGIN DBMS_OUTPUT.PUT_LINE('Student name is ' || p1); END; END pkg3; / \set SQLTERM ;
结果:
CREATE PACKAGE BODY
当一个会话中引用包中对象时,数据库会为该会话对包进行实例化。包的实例化在每个不同的会话中都是独立的。
当数据库实例化一个包时,它会对其进行初始化。初始化包括以下工作:
为公共常量分配初始值
为公共变量分配初始值
执行包体的初始化部分
熟悉 KES 数据库提供的包,避免编写重复其特性的包。
有关 KES 数据库提供的包的更多信息,请参阅 KES 数据库 系统包 。
使包具有通用性,以便跟多的应用程序可以复用它们。
在编写包体之前设计和编写包规范。
在包规范中,仅声明调用程序必须可见的项。
这种做法可以防止其他开发人员在您的实现细节上构建不安全的依赖关系,并减少重新编译。
如果更改包规范,则必须重新编译所有调用包的公共子程序的数据库对象。如果只更改包体,则无需重新编译这些对象。
在包体的初始化部分中分配初始值,而不是在包声明中分配。
这种做法有以下优点:
用于计算初始值的代码可以更复杂,且记录的更完善。
如果计算初始值引发异常,则初始化部分可以使用自己的异常处理程序来处理它。
示例4 创建两个表 student、log 和一个包 emp_admin ,然后从一个匿名块中调用包中的子程序,包 emp_admin 既有包规范又有包体。
包 emp_admin 的包规范声明了一个公共类型、游标和异常,以及三个公共的子程序。其中一个公共子程序被重载(有关重载子程序的信息,请参阅 重载子程序 )。
包体定义了包规范中声明的子程序,声明并定义了一个私有变量,一个私有函数,并拥有一个初始化块。
初始化部分(仅在匿名块第一次引用包时运行)向表 log 中插入一行数据并将私有变量 number_hired 初始化为零。每次调用包过程 hire_student 时,它都会更新私有变量 number_hired 的值。
示例 4 创建包 emp_admin
set serverout on -- 创建表: DROP TABLE IF EXISTS student CASCADE; CREATE TABLE student ( id INT PRIMARY KEY, name TEXT, score NUMBER ); DROP TABLE IF EXISTS log; CREATE TABLE log ( date_of_action DATE, user_id VARCHAR2(20), package_name VARCHAR2(30) ); -- 包规范 \set SQLTERM / CREATE OR REPLACE PACKAGE emp_admin AUTHID DEFINER AS -- 声明公共类型、游标、异常: TYPE RecTyp IS RECORD (id INT, score NUMBER); CURSOR desc_score RETURN RecTyp IS SELECT id, score FROM student ORDER BY score DESC; invalid_score EXCEPTION; -- 声明公共子程序: FUNCTION hire_student ( name VARCHAR2, score NUMBER ) RETURN NUMBER; -- 重载的公共子程序: PROCEDURE fire_student (emp_id INT); PROCEDURE fire_student (emp_name VARCHAR2); PROCEDURE raise_score (emp_id INT, emp_score NUMBER); FUNCTION highest_score (n NUMBER) RETURN RecTyp; END emp_admin; / \set SQLTERM ; -- 包体: \set SQLTERM / CREATE OR REPLACE PACKAGE BODY emp_admin AS number_hired NUMBER; -- 私有变量,仅包体可见 -- 定义在包中声明的子程序: FUNCTION hire_student ( name VARCHAR2, score NUMBER ) RETURN NUMBER IS BEGIN INSERT INTO student ( id, name, score ) VALUES ( number_hired, hire_student.name, hire_student.score ); DBMS_OUTPUT.PUT_LINE('The number of student is ' || TO_CHAR(number_hired) ); number_hired := number_hired + 1; RETURN number_hired - 1; END hire_student; PROCEDURE fire_student (emp_id NUMBER) IS BEGIN DELETE FROM student WHERE id = emp_id; END fire_student; PROCEDURE fire_student (emp_name VARCHAR2) IS BEGIN DELETE FROM student WHERE name = emp_name; END fire_student; -- 私有函数,仅包体可见: FUNCTION score_ok ( score NUMBER ) RETURN BOOLEAN IS BEGIN RETURN (score >= 0) AND (score <= 100); END score_ok; PROCEDURE raise_score ( emp_id INT, emp_score NUMBER ) IS BEGIN IF score_ok(emp_score) THEN -- 调用私有函数 UPDATE student SET score = emp_score WHERE id = emp_id; ELSE RAISE invalid_score; END IF; EXCEPTION WHEN invalid_score THEN DBMS_OUTPUT.PUT_LINE ('The score is out of the specified range.'); END raise_score; FUNCTION highest_score ( n NUMBER ) RETURN RecTyp IS emp_rec RecTyp; BEGIN OPEN desc_score; FETCH desc_score INTO emp_rec; CLOSE desc_score; RETURN emp_rec; END highest_score; BEGIN -- 包体的初始化块 INSERT INTO log (date_of_action, user_id, package_name) VALUES (SYSDATE, USER, 'EMP_ADMIN'); number_hired := 1; END emp_admin; / \set SQLTERM ; -- 在匿名块中调用包中子程序: \set SQLTERM / DECLARE new_emp_id NUMBER(6); highest_student emp_admin.RecTyp; BEGIN new_emp_id := emp_admin.hire_student ( 'Zhang San', 0 ); DBMS_OUTPUT.PUT_LINE ('The student id is ' || TO_CHAR(new_emp_id)); emp_admin.raise_score (new_emp_id, 90); highest_student := emp_admin.highest_score(1); DBMS_OUTPUT.PUT_LINE ( 'The highest score is '|| TO_CHAR (highest_student.score) || ', belonging to student: ' || TO_CHAR (highest_student.id) ); emp_admin.fire_student(new_emp_id); -- 也可以调用以下子程序: -- emp_admin.fire_student('Zhang San'); END; / \set SQLTERM ;
输出结果:
The number of student is 1 The student id is 1 The highest score is 90, belonging to student: 1
KES提供了一个名为 STANDARD 的系统包,该包中定义了部分可用于 PL/SQL 环境中的对象。例如, STANDARD 包声明 too_many_rows 等预定义异常。