什么是包
包是一种数据库对象,相当于一个容器。将逻辑上相关的过程、函数、变量等元素组合成一个更大的单元。
包的组成
包由包头和包体两部分组成。包头中声明的元素是公有对象,对外部可见;包体中声明的元素为该包的私有对象,仅该包可见;包体中自定义所有公有对象和私有对象。
包头中可以声明公有变量、常量、类型、自定义异常、游标和子程序。
包体中可以声明私有变量、常量、类型、自定义异常、游标和子程序。也可以定义上述所有对象,另外,包体中可以定义自己的初始化块,初始化块仅在创建时执行,可以在创建时将包中定义的变量等初始化。
包的优点
1.简化应用设计
包将一个模块的 PLSQL 程序元素组织到一起,每个包相互独立,便于管理。
2.提高应用性能
包在首次调用时,就整体存入内存,之后的访问都不需要再进行 I/O 操作。
3.实现信息隐藏
包中分为公有和私有部分,只对外提供公有的接口,私有的内容对外不可见。
4.解决命名冲突
在不同的包中,过程、函数、变量等元素都可以重名,解决了命名冲突问题。
包的使用规则
包头可以单独存在,但是包体的定义一定要在包头定义之后。
包体中必须定义所有包头中声明的对象。
允许创建不包含任何函数或存储过程的包头和包体。
包体中的私有元素不能被外部直接使用,但可以通过公有子程序间接使用。
如果更新包头,则必须重新编译包体后才能正常使用;如果仅更改包体,则无需重新编译包头。
包的使用规则场景
1. 单独定义一个包头,正确执行;单独定义一个包体,报错。
--定义包头,创建成功
test=# CREATE OR REPLACE PACKAGE pkg1 AS
test-# i int;
test-# PROCEDURE pt1();
test-# END;
test-# /
CREATE PACKAGE
--定义包体,报错
test=# CREATE OR REPLACE PACKAGE BODY pkg2 AS
test-# PROCEDURE pt1() AS
test-# BEGIN
test-# NULL;
test-# END;
test-# END;
test-# /
ERROR: package "pkg2" does not exist
复制代码
2. 包体中未定义包头中所有声明的对象
--创建一个包头,声明两个存储过程pt1,pt2
test=# CREATE OR REPLACE PACKAGE pkg3 AS
test-# PROCEDURE pt1();
test-# PROCEDURE pt2();
test-# END;
test-# /
CREATE PACKAGE
--创建对应的包体,只定义其中一个存储过程pt1。报错提示pt2也必须在包体中定义
test=# CREATE OR REPLACE PACKAGE BODY pkg3 AS
test-# PROCEDURE pt1() AS --只定义了pt1,未定义pt2
test-# BEGIN
test-# RAISE NOTICE 'pt1';
test-# END;
test-# END;
test-# /
ERROR: procedure "pt2" is declared in package "pkg3" and must be defined in the package body
复制代码
3. 创建一个包头和一个包体,其中不声明定义任何元素
--先创建包头,不声明任何元素,创建成功
test=# CREATE OR REPLACE PACKAGE pkg4 AS
test-# END;
test-# /
CREATE PACKAGE
--再创建包体,不声明定义任何元素,创建成功
test=# CREATE OR REPLACE PACKAGE BODY pkg4 AS
test-# END;
test-# /
CREATE PACKAGE BODY
复制代码
4. 包体中定义私有元素无法再外部直接调用,但是可以通过公有子程序间接调用
--创建一个包头,其中声明了一个公有存储过程
test=# CREATE OR REPLACE PACKAGE pkg5 AS
test-# PROCEDURE pubilc_procedure();
test-# END;
test-# /
CREATE PACKAGE
--创建一个包体,其中声明了一个私有变量,定义了一个包头中声明的公有过程和一个包头中未声明的私有过程(注意:私有子程序必须定义在公有子程序之前才能被公有子程序调用)
test=# CREATE OR REPLACE PACKAGE BODY pkg5 AS
test-# i int; --定义一个私有变量
test-# PROCEDURE private_procedure() AS --定义一个私有存储过程
test-# BEGIN
test-# RAISE NOTICE 'this is a private procedure!';
test-# END;
test-# PROCEDURE pubilc_procedure() AS --定义包头中声明的公有存储过程
test-# BEGIN
test-# private_procedure(); --通过公有存储过程调用私有存储过程
test-# RAISE NOTICE 'private var i = %',i; --通过公有存储过程调用私有变量
test-# END;
test-# BEGIN
test-# i = 1; --通过初始化块对私有变量进行初始化
test-# END;
test-# /
CREATE PACKAGE BODY
--在外部直接调用包的私有过程,报错找不到对应模式或者包下的过程
test=# call pkg5.private_procedure();
test-# /
ERROR: schema or package "pkg5" does not exist
LINE 1: call pkg5.private_procedure();
--通过在外部调用包公有过程间接调用包的私有过程和变量,调用成功
test=# call pkg5.pubilc_procedure();
test-# /
NOTICE: this is a private procedure!
NOTICE: private var i = 1
CALL
复制代码
5. 更新包头后,无论是否对包体中的定义有影响,都需要重新编译包体才能正常调用
--创建一个包头,其中只声明了一个存储过程
test=# CREATE OR REPLACE PACKAGE pkg7 AS
test-# PROCEDURE pt1();
test-# END;
test-# /
CREATE PACKAGE
--创建一个包体,定义包头中声明的存储过程
test=# CREATE OR REPLACE PACKAGE BODY pkg7 AS
test-# PROCEDURE pt1() AS
test-# BEGIN
test-# RAISE NOTICE 'test';
test-# END;
test-# END;
test-# /
CREATE PACKAGE BODY
--调用包中存储过程,可正常执行
test=# call pkg7.pt1();
test-# /
NOTICE: test
CALL
--修改包头,增加一个变量的声明,对原有存储过程不改动
test=# CREATE OR REPLACE PACKAGE pkg7 AS
test-# i int;
test-# PROCEDURE pt1();
test-# END;
test-# /
CREATE PACKAGE
--再次调用包中存储过程,报错
test=# call pkg7.pt1();
test-# /
ERROR: function (or procedure) "pt1" declared in package "pkg7" has not defined
LINE 1: call pkg7.pt1();
--重新编译包体,不做任何改动
test=# CREATE OR REPLACE PACKAGE BODY pkg7 AS
test-# PROCEDURE pt1() AS
test-# BEGIN
test-# RAISE NOTICE 'test';
test-# END;
test-# END;
test-# /
CREATE PACKAGE BODY
--再次调用包中存储过程,正确执行
test=# call pkg7.pt1();
test-# /
NOTICE: test
CALL
--修改包体定义的存储过程中的执行内容,不需要重新编译包头
test=# CREATE OR REPLACE PACKAGE BODY pkg7 AS
test-# PROCEDURE pt1() AS
test-# BEGIN
test-# RAISE NOTICE 'newtest'; --修改NOTICE内容
test-# END;
test-# END;
test-# /
CREATE PACKAGE BODY
--调用包中存储过程,正常执行且为修改后的内容
test=# call pkg7.pt1();
test-# /
NOTICE: newtest
CALL
复制代码
举例
为什么要使用包
在开发大型项目时会用到很多模块,每个模块都有自己的函数、存储过程、变量等元素,这些元素如果都放在一起,那么查询、维护起来就会很困难,这时候就需要使用包分类管理不同模块的元素。
假设一个场景,当我们需要做一个学校的管理系统,其中有需要对学生和教师进行评分的功能,对于学生和教师都有相似的行为,比如显示分数,修改分数等,但是其内部功能又不一样,需要不同的函数实现。如果创建几个名字相似的函数使用和管理起来比较容易混淆,而且后期需要移植也还需要确定哪些函数是相关的;这时候就可以通过创建两个不同的包,将相关的函数、变量都独立放在包里,每个包中的行为都很明确,而且移植起来只需要把整个包移植过去,使用和管理起来都很方便。
包的应用场景
引用包中元素的方式有很多种,其中最基础的方式是:包名.元素名
创建管理学生信息的包 student,它具有从 stu 表中获取学生信息,显示学生信息,修改学生得分,和写回 stu 表的功能。
管理教师信息的包 teacher 创建也类似如下方式,只是根据情况修改包体中的实现。
--打开DBMS_OUTPUT信息输出
test=# set serverout on
--创建学生表并插入数据
test=# CREATE TABLE stu (id int primary key, name text, score number);
test-# insert into stu values (1, 'zs', 70);
test-# insert into stu values (2, 'ls', 80);
test-# insert into stu values (3, 'ww', 90);
test-# /
INSERT 0 1
--创建管理学生信息包的包头,声明管理功能需要的过程
test=# CREATE OR REPLACE PACKAGE student AS
test-# PROCEDURE GET_STUDENT(t_id number); --获取学生的信息
test-# PROCEDURE SHOW_INFO(); --显示学生的信息
test-# PROCEDURE CHANGE_SCORE(newscore NUMBER); --修改学生的分数
test-# PROCEDURE SAVE_STUDENT(); --保存学生的信息
test-# END;
test-# /
CREATE PACKAGE
--创建管理学生信息包的包体,声明一个私有变量存储学生信息,定义包头中声明的过程
test=# CREATE OR REPLACE PACKAGE BODY student AS
test-# p_id int; --私有变量,只用在包体过程定义中存储学生的信息
test-# p_name text;
test-# p_score number;
test-# --将变量存储的学生信息打印出来
test-# PROCEDURE SHOW_INFO AS
test-# BEGIN
test-# DBMS_OUTPUT.PUT_LINE('---student info---');
test-# DBMS_OUTPUT.PUT_LINE('student id:'|| p_id);
test-# DBMS_OUTPUT.PUT_LINE('student name:'|| p_name);
test-# DBMS_OUTPUT.PUT_LINE('student score:'|| p_score);
test-# END;
test-#
test-# --从学生表中查出信息,存入变量中
test-# PROCEDURE GET_STUDENT(t_id number) AS
test-# BEGIN
test-# SELECT id, name, score INTO p_id, p_name, p_score FROM stu WHERE id = t_id;
test-# DBMS_OUTPUT.PUT_LINE('---successful get student info---');
test-# END;
test-#
test-# --改变变量的信息,未保存到表中
test-# PROCEDURE CHANGE_SCORE(newscore number) AS
test-# BEGIN
test-# p_score = newscore;
test-# DBMS_OUTPUT.PUT_LINE('---successful change student score---');
test-# END;
test-#
test-# --将变量中的信息更新保存到学生表中
test-# PROCEDURE SAVE_STUDENT() AS
test-# BEGIN
test-# UPDATE stu SET score = p_score WHERE id = p_id;
test-# DBMS_OUTPUT.PUT_LINE('---successful set student info---');
test-# END;
test-# BEGIN
test-# --初始化块,对包中变量进行初始化
test-# p_id = 0;
test-# p_name = '';
test-# p_score = -1;
test-# END;
test-# /
CREATE PACKAGE BODY
--调用包中打印函数,打印学生的信息,当前还未获取学生的信息,打印的值为初始化值
test=# call student.SHOW_INFO();
test-# /
CALL
---student info---
student id:0
student name:
student score:-1
--调用包中获取学生信息函数,从学生表中获取信息存入包的私有变量
test=# call student.GET_STUDENT(1);
test-# /
CALL
---successful get student info---
--再次调用包中打印函数,打印当前获取的学生信息
test=# call student.SHOW_INFO();
test-# /
CALL
---student info---
student id:1
student name:zs
student score:70
--调用包中修改学生信息函数,传入需要修改的分数值
test=# call student.CHANGE_SCORE(99);
test-# /
CALL
---successful change student score---
--调用包中打印函数,打印修改过程的学生信息
test=# call student.SHOW_INFO();
test-# /
CALL
---student info---
student id:1
student name:zs
student score:99
--调用包中保存信息函数,将修改的学生信息更新到学生表中
test=# call student.SAVE_STUDENT();
test-# /
CALL
---successful set student info---
--查看学生表中对应学生信息,信息已改变
test=# select * from stu where id = 1;
test-# /
id | name | score
----+------+-------
1 | zs | 99
(1 row)
复制代码
评论