写惯了C++/Java代码,心里就想着什么都是面向对象,就想着封装。当在工作中写多了存储过程,函数的时候,发现你写的东西都是分散在系统中的,并没有按照面向对象的思想组织起来,写的多了,发现自己写的那些存储过程,函数都不好管理。后来查资料,发现Oracle中有“包”这么个概念,具有面向对象的“味道”,所以就找时间看了看Oracle中的包,总结成一篇文章,也是自己学习成果的总结。
包也是模式对象,它是将相关元素集成在一起的PL/SQL结构。包由以下两部分组成:
包头和包体是单独存放在数据库的字典中。在包中,我们可以集成以下对象:
像中国移动这种大型的电信公司,其业务复杂程度不是你可以想象的,每一个功能模块都是由N个存储过程、函数等组成的,而这些过程、函数默认是放在一起的(如在PL/SQL Developer中,过程默认都是放在一起的,即Procedures中),这些非常不方便查询和维护,甚至会发生误删除的事件。如果将这些存储过程、函数等按照功能模块进行划分,分别封装在不同的包中,那后期对这些功能模块进行维护时,模块之间将会变得异常清晰,极大的降低维护成本。先来创建一个包吧。
上面说了包由包头和包体两部分组成,包头就好比类的声明文件,包体就好比类的定义文件。
select
语句。如果包头定义了存储过程或者函数,则要在包体中编写实现这些存储过程或者函数的代码。对于高级语言中的权限控制,在Oracle的包中也存在这样的概念。在包头中定义的对象,包外面的程序可以对其进行引用;当然了,包内的存储过程、函数等也可以对其进行引用;在包体中定义的对象只能在包体中进行引用,包外的对象不能对它们进行引用。这就是Oralce包中最简单的访问权限控制。
包是由包头和包体两部分创建的,所以在创建包的过程中也分为以下两部分:
创建一个包头:
create or replace package pack_test as
b varchar2(10); -- 定义变量
c_id constant number := 100; -- 定义常量
type employ_info is record -- 定义类型
(
employ_id varchar2(10),
salary number(20, 2)
);
cursor employ_cur return employ_info; -- 定义游标变量
invalid_salary exception; -- 定义异常
function hire_employee(name in varchar2, age number, sex varchar2) return int;
procedure fire_employee(emp_id varchar2); -- 定义存储过程
end;
可以看到,我在包头中指定定义了变量、函数和存储过程,并没有实现函数和存储过程。在包体中我们将对定义的游标、函数和存储过程进行编码实现。接下来看看包体的实现。
create or replace package body pack_test as
cursor employ_cur return employ_info
is select emp_id, salary from employee;
default_salary constant int := 5000; -- 包体中定义的变量,相当于私有变量,包外无法引用
total_count int;
-- 定义函数
function hire_employee(name in varchar2, age number, sex varchar2) return int is
str constant varchar2(50) := 'Call Function';
begin
dbms_output.put_line(str);
dbms_output.put_line('Name is :' || name || '; Age is :' || age || '; Sex is :' || sex);
return 1;
end hire_employee;
-- 定义存储过程
procedure fire_employee(emp_id varchar2) is
str constant varchar2(50) := 'Call procedure';
begin
dbms_output.put_line(str);
dbms_output.put_line('Employee ID is :' || emp_id);
end fire_employee;
begin
-- 这里可以对一些变量进行初始化
-- 这些变量的初始化发生在第一次引用包中对象时
total_count := 0;
end pack_test;
上面的包体就是把包头中的定义进行了简单的实现;但是有一点需要特别注意,就是包体的包名和包头的包名必须一致,否则就不发找到包头对应的包体定义了。关于其它的就没有多少好说的了,只要看过之前的文章,上述的代码就轻车熟路了,你只需要掌握定义包的语法格式即可。
定义完包以后,就需要对其进行编译,将其保存到数据字典中。由于包由包头和包体两部分组成,我们既可以对整个包进行整体编译,也可以单独对包头或者包体进行编译。
alter package pack_test compile package;
alter package pack_test compile specification;
alter package pack_test compile body;
当你在编译包的时候,可以使用以下语句查看具体的错误:
show errors package <包名>;
show errors package body <包名>;
好了,包头和包体都已经编写完毕,也已经正确的编译通过了,接下来就轮到我们去调用这个包中的内容了。调用包中的函数或存储过程也分为以下两种情况:
当使用SQL命令直接调用包中对象时,需要使用execute
命令,例如:
execute pack_test.fire_employee('3008732');
在PL/SQL中调用包中对象,直接调用即可,例如:
-- Created on 9/9/2015 by JELLY
declare
-- Local variables here
i integer;
begin
-- Test statements here
i := pack_test.hire_employee('Jelly', 23, 'M');
dbms_output.put_line(i);
pack_test.fire_employee('660045214');
end;
可以看到,调用包中的函数或存储过程就像调用其它的函数和存储过程一样,只是多了一个包名限定。
当你觉的这个包有点多余了,你完全可以删除它。由于包是由包头和包体两部分组成的,所以删除包的时候,多多少少和删除函数或存储过程是有点区别的。
drop package body pack_test;
这样就删除了包体。而如果需要删除整个包,则需要:
drop package pack_test;
当然了,执行这一切的前提是你必须具有drop any procedure
权限。
简单说一下Oracle中重载的概念。
多个具有相同名字的函数或存储过程,但是它们的参数个数或参数类型不同。当调用这样的函数或存储过程时,即使存储过程或者函数的名字相同,根据不同的参数个数或参数类型,Oracle会自动调用相应的存储过程或者函数。
这样的概念和C++中的重载是一个意思。当我看到Oracle包中居然支持重载时,我是很吃惊的。是的,它支持重载,虽然仅仅只在包中支持。对于Oracle中重载有几点需要注意。
procedure proc_1(param in number);
procedure proc_1(param out number);
亲爱的,这不是重载。
procedure proc_1(p in char);
procedure proc_1(p in varchar2);
由于char和varchar2同属于字符型的参数,这样也不能算作重载。
Oracle中的包看起来不错,我好喜欢。在做下一个功能的时候,看看能不能完美的使用以下包。学习就是这样,学了就要去用,不用的话,你永远不知道你不会。
2015年9月9日 于呼和浩特。