寫慣了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中的包看起來不錯,我好喜歡。在做下一個功能的時候,看看能不能完美的使用以下包。學習就是這樣,學了就要去用,不用的話,你永遠不知道你不會。