包的概念和組成 包是用來存儲相關程序結構的對象,它存儲於數據字典中。包由兩個分離的部分組成:包頭(PACKAGE)和包體(PACKAGE BODY)。包頭是包的說明部分,是對外的操作接口,對應用是可見的;包體是包的代碼和實現部分,對應用來說是不可見的黑盒。 包中可以包含的程序結構如下所示。 過程(PROCUDURE) 帶參數的命名的程序模塊 函數(FUNCTION) 帶參數、具有返回值的命名的程序模塊 變量(VARIABLE) 存儲變化的量的存儲單元 常量(CONSTANT) 存儲不變的量的存儲單元 游標(CURSOR) 用戶定義的數據操作緩存區,在可執行部分使用 類型(TYPE) 用戶定義的新的結構類型 異常(EXCEPTION) 在標准包中定義或由用戶自定義,用於處理程序錯誤 說明部分可以出現在包的三個不同的部分:出現在包頭中的稱為公有元素,出現在包體中的稱為私有元素,出現在包體的過程(或函數)中的稱為局部變量。它們的性質有所不同,如下所示。 公有元素(PUBLIC) 在包頭中說明,在包體中具體定義 在包外可見並可以訪問,對整個應用的全過程有效 私有元素(PRIVATE) 在包體的說明部分說明 只能被包內部的其他部分訪問 局部變量(LOCAL) 在過程或函數的說明部分說明 只能在定義變量的過程或函數中使用 在包體中出現的過程或函數,如果需要對外公用,就必須在包頭中說明,包頭中的說明應該和包體中的說明一致。 包有以下優點: * 包可以方便地將存儲過程和函數組織到一起,每個包又是相互獨立的。在不同的包中,過程、函數都可以重名,這解決了在同一個用戶環境中命名的沖突問題。 * 包增強了對存儲過程和函數的安全管理,對整個包的訪問權只需一次授予。 * 在同一個會話中,公用變量的值將被保留,直到會話結束。 * 區分了公有過程和私有過程,包體的私有過程增加了過程和函數的保密性。 * 包在被首次調用時,就作為一個整體被全部調入內存,減少了多次訪問過程或函數的I/O次數。 創建包和包體 包由包頭和包體兩部分組成,包的創建應該先創建包頭部分,然后創建包體部分。創建、刪除和編譯包的權限同創建、刪除和編譯存儲過程的權限相同。 創建包頭的簡要語句如下: CREATE [OR REPLACE] PACKAGE 包名 {IS|AS} 公有變量定義 公有類型定義 公有游標定義 公有異常定義 函數說明 過程說明 END; 創建包體的簡要語法如下: CREATE [OR REPLACE] PACKAGE BODY 包名 {IS|AS} 私有變量定義 私有類型定義 私有游標定義 私有異常定義 函數定義 過程定義 END; 包的其他操作命令包括: 刪除包頭: DROP PACKAGE 包頭名 刪除包體: DROP PACKAGE BODY 包體名 重新編譯包頭: ALTER PACKAGE 包名 COMPILE PACKAGE 重新編譯包體: ALTER PACKAGE 包名 COMPILE PACKAGE BODY 在包頭中說明的對象可以在包外調用,調用的方法和調用單獨的過程或函數的方法基本相同,惟一的區別就是要在調用的過程或函數名前加上包的名字(中間用“.”分隔)。但要注意,不同的會話將單獨對包的公用變量進行初始化,所以不同的會話對包的調用屬於不同的應用。 系統包 Oracle預定義了很多標准的系統包,這些包可以在應用中直接使用,比如在訓練中我們使用的DBMS_OUTPUT包,就是系統包。PUT_LINE是該包的一個函數。常用系統包下所示。 DBMS_OUTPUT 在SQL*Plus環境下輸出信息 DBMS_DDL 編譯過程函數和包 DBMS_SESSION 改變用戶的會話,初始化包等 DBMS_TRANSACTION 控制數據庫事務 DBMS_MAIL 連接Oracle*Mail DBMS_LOCK 進行復雜的鎖機制管理 DBMS_ALERT 識別數據庫事件告警 DBMS_PIPE 通過管道在會話間傳遞信息 DBMS_JOB 管理Oracle的作業 DBMS_LOB 操縱大對象 DBMS_SQL 執行動態SQL語句 包的應用 在SQL*Plus環境下,包和包體可以分別編譯,也可以一起編譯。如果分別編譯,則要先編譯包頭,后編譯包體。如果在一起編譯,則包頭寫在前,包體在后,中間用“/”分隔。 可以將已經存在的存儲過程或函數添加到包中,方法是去掉過程或函數創建語句的CREATE OR REPLACE部分,將存儲過程或函數復制到包體中 ,然后重新編譯即可。 如果需要將私有過程或函數變成共有過程或函數的話,將過程或函數說明部分復制到包頭說明部分,然后重新編譯就可以了。 【訓練1】 創建管理雇員信息的包EMPLOYE,它具有從EMP表獲得雇員信息,修改雇員名稱,修改雇員工資和寫回EMP表的功能。 步驟1:登錄SCOTT賬戶,輸入以下代碼並編譯: CREATE OR REPLACE PACKAGE EMPLOYE --包頭部分 IS PROCEDURE SHOW_DETAIL; PROCEDURE GET_EMPLOYE(P_EMPNO NUMBER); PROCEDURE SAVE_EMPLOYE; PROCEDURE CHANGE_NAME(P_NEWNAME VARCHAR2); PROCEDURE CHANGE_SAL(P_NEWSAL NUMBER); END EMPLOYE; / --包頭和包體分隔符 CREATE OR REPLACE PACKAGE BODY EMPLOYE --包體部分 IS EMPLOYE EMP%ROWTYPE; ------------------ 顯示雇員信息 --------------- PROCEDURE SHOW_DETAIL AS BEGIN DBMS_OUTPUT.PUT_LINE(‘----- 雇員信息 -----’); DBMS_OUTPUT.PUT_LINE('雇員編號:'||EMPLOYE.EMPNO); DBMS_OUTPUT.PUT_LINE('雇員名稱:'||EMPLOYE.ENAME); DBMS_OUTPUT.PUT_LINE('雇員職務:'||EMPLOYE.JOB); DBMS_OUTPUT.PUT_LINE('雇員工資:'||EMPLOYE.SAL); DBMS_OUTPUT.PUT_LINE('部門編號:'||EMPLOYE.DEPTNO); END SHOW_DETAIL; ----------------- 從EMP表取得一個雇員 -------------------- PROCEDURE GET_EMPLOYE(P_EMPNO NUMBER) AS BEGIN SELECT * INTO EMPLOYE FROM EMP WHERE EMPNO=P_EMPNO; DBMS_OUTPUT.PUT_LINE('獲取雇員'||EMPLOYE.ENAME||'信息成功'); EXCEPTION WHEN OTHERS THEN DBMS_OUTPUT.PUT_LINE('獲取雇員信息發生錯誤!'); END GET_EMPLOYE; ---------------------- 保存雇員到EMP表 -------------------------- PROCEDURE SAVE_EMPLOYE AS BEGIN UPDATE EMP SET ENAME=EMPLOYE.ENAME, SAL=EMPLOYE.SAL WHERE EMPNO=EMPLOYE.EMPNO; DBMS_OUTPUT.PUT_LINE('雇員信息保存完成!'); END SAVE_EMPLOYE; ---------------------------- 修改雇員名稱 ------------------------------ PROCEDURE CHANGE_NAME(P_NEWNAME VARCHAR2) AS BEGIN EMPLOYE.ENAME:=P_NEWNAME; DBMS_OUTPUT.PUT_LINE('修改名稱完成!'); END CHANGE_NAME; ---------------------------- 修改雇員工資 -------------------------- PROCEDURE CHANGE_SAL(P_NEWSAL NUMBER) AS BEGIN EMPLOYE.SAL:=P_NEWSAL; DBMS_OUTPUT.PUT_LINE('修改工資完成!'); END CHANGE_SAL; END EMPLOYE; 步驟2:獲取雇員7788的信息: SET SERVEROUTPUT ON EXECUTE EMPLOYE.GET_EMPLOYE(7788); 結果為: 獲取雇員SCOTT信息成功 PL/SQL 過程已成功完成。 步驟3:顯示雇員信息: EXECUTE EMPLOYE.SHOW_DETAIL; 結果為: ------------------ 雇員信息 ------------------ 雇員編號:7788 雇員名稱:SCOTT 雇員職務:ANALYST 雇員工資:3000 部門編號:20 PL/SQL 過程已成功完成。 步驟4:修改雇員工資: EXECUTE EMPLOYE.CHANGE_SAL(3800); 結果為: 修改工資完成! PL/SQL 過程已成功完成。 步驟5:將修改的雇員信息存入EMP表 EXECUTE EMPLOYE.SAVE_EMPLOYE; 結果為: 雇員信息保存完成! PL/SQL 過程已成功完成。 說明:該包完成將EMP表中的某個雇員的信息取入內存記錄變量,在記錄變量中進行修改編輯,在確認顯示信息正確后寫回EMP表的功能。記錄變量EMPLOYE用來存儲取得的雇員信息,定義為私有變量,只能被包的內部模塊訪問。 階段訓練 下面的訓練通過定義和創建完整的包EMP_PK並綜合運用本章的知識,完成對雇員表的插入、刪除等功能,包中的主要元素解釋如下所示。 程序結構 類 型 說 明 V_EMP_COUNT 公有變量 跟蹤雇員的總人數變化,插入、刪除雇員的同時修改該變量的值 INIT 公有過程 對包進行初始化,初始化雇員人數和工資修改的上、下限 LIST_EMP 公有過程 顯示雇員列表 INSERT_EMP 公有過程 通過編號插入新雇員 DELETE_EMP 公有過程 通過編號刪除雇員 CHANGE_EMP_SAL 公有過程 通過編號修改雇員工資 V_MESSAGE 私有變量 存放准備輸出的信息 C_MAX_SAL 私有變量 對工資修改的上限 C_MIN_SAL 私有變量 對工資修改的下限 SHOW_MESSAGE 私有過程 顯示私有變量V_MESSAGE中的信息 EXIST_EMP 私有函數 判斷某個編號的雇員是否存在,該函數被INSERT_EMP、DELETE_EMP和CHANGE_EMP_SAL等過程調用 【訓練1】 完整的雇員包EMP_PK的創建和應用。 步驟1:在SQL*Plus中登錄SCOTT賬戶,輸入以下包頭和包體部分,按“執行”按鈕編譯: CREATE OR REPLACE PACKAGE EMP_PK --包頭部分 IS V_EMP_COUNT NUMBER(5); --雇員人數 PROCEDURE INIT(P_MAX NUMBER,P_MIN NUMBER); --初始化 PROCEDURE LIST_EMP; --顯示雇員列表 PROCEDURE INSERT_EMP(P_EMPNO NUMBER,P_ENAME VARCHAR2,P_JOB VARCHAR2,P_SAL NUMBER); --插入雇員 PROCEDURE DELETE_EMP(P_EMPNO NUMBER); --刪除雇員 PROCEDURE CHANGE_EMP_SAL(P_EMPNO NUMBER,P_SAL NUMBER); --修改雇員工資 END EMP_PK; / CREATE OR REPLACE PACKAGE BODY EMP_PK --包體部分 IS V_MESSAGE VARCHAR2(50); --顯示信息 V_MAX_SAL NUMBER(7); --工資上限 V_MIN_SAL NUMBER(7); --工資下限 FUNCTION EXIST_EMP(P_EMPNO NUMBER) RETURN BOOLEAN; --判斷雇員是否存在函數 PROCEDURE SHOW_MESSAGE; --顯示信息過程 ------------------------------- 初始化過程 ---------------------------- PROCEDURE INIT(P_MAX NUMBER,P_MIN NUMBER) IS BEGIN SELECT COUNT(*) INTO V_EMP_COUNT FROM EMP; V_MAX_SAL:=P_MAX; V_MIN_SAL:=P_MIN; V_MESSAGE:='初始化過程已經完成!'; SHOW_MESSAGE; END INIT; ---------------------------- 顯示雇員列表過程 --------------------- PROCEDURE LIST_EMP IS BEGIN DBMS_OUTPUT.PUT_LINE('姓名 職務 工資'); FOR emp_rec IN (SELECT * FROM EMP) LOOP DBMS_OUTPUT.PUT_LINE(RPAD(emp_rec.ename,10,'')||RPAD(emp_rec.job,10,' ')||TO_CHAR(emp_rec.sal)); END LOOP; DBMS_OUTPUT.PUT_LINE('雇員總人數'||V_EMP_COUNT); END LIST_EMP; ----------------------------- 插入雇員過程 ----------------------------- PROCEDUREINSERT_EMP(P_EMPNO NUMBER,P_ENAMEVARCHAR2,P_JOB VARCHAR2,P_SAL NUMBER) IS BEGIN IF NOT EXIST_EMP(P_EMPNO) THEN INSERT INTO EMP(EMPNO,ENAME,JOB,SAL) VALUES(P_EMPNO,P_ENAME,P_JOB,P_SAL); COMMIT; V_EMP_COUNT:=V_EMP_COUNT+1; V_MESSAGE:='雇員'||P_EMPNO||'已插入!'; ELSE V_MESSAGE:='雇員'||P_EMPNO||'已存在,不能插入!'; END IF; SHOW_MESSAGE; EXCEPTION WHEN OTHERS THEN V_MESSAGE:='雇員'||P_EMPNO||'插入失敗!'; SHOW_MESSAGE; END INSERT_EMP; --------------------------- 刪除雇員過程 -------------------- PROCEDURE DELETE_EMP(P_EMPNO NUMBER) IS BEGIN IF EXIST_EMP(P_EMPNO) THEN DELETE FROM EMP WHERE EMPNO=P_EMPNO; COMMIT; V_EMP_COUNT:=V_EMP_COUNT-1; V_MESSAGE:='雇員'||P_EMPNO||'已刪除!'; ELSE V_MESSAGE:='雇員'||P_EMPNO||'不存在,不能刪除!'; END IF; SHOW_MESSAGE; EXCEPTION WHEN OTHERS THEN V_MESSAGE:='雇員'||P_EMPNO||'刪除失敗!'; SHOW_MESSAGE; END DELETE_EMP; --------------------------------------- 修改雇員工資過程 ------------------------------------ PROCEDURE CHANGE_EMP_SAL(P_EMPNO NUMBER,P_SAL NUMBER) IS BEGIN IF (P_SAL>V_MAX_SAL OR P_SAL<V_MIN_SAL) THEN V_MESSAGE:='工資超出修改范圍!'; ELSIF NOT EXIST_EMP(P_EMPNO) THEN V_MESSAGE:='雇員'||P_EMPNO||'不存在,不能修改工資!'; ELSE UPDATE EMP SET SAL=P_SAL WHERE EMPNO=P_EMPNO; COMMIT; V_MESSAGE:='雇員'||P_EMPNO||'工資已經修改!'; END IF; SHOW_MESSAGE; EXCEPTION WHEN OTHERS THEN V_MESSAGE:='雇員'||P_EMPNO||'工資修改失敗!'; SHOW_MESSAGE; END CHANGE_EMP_SAL; ---------------------------- 顯示信息過程 ---------------------------- PROCEDURE SHOW_MESSAGE IS BEGIN DBMS_OUTPUT.PUT_LINE('提示信息:'||V_MESSAGE); END SHOW_MESSAGE; ------------------------ 判斷雇員是否存在函數 ------------------- FUNCTION EXIST_EMP(P_EMPNO NUMBER) RETURN BOOLEAN IS V_NUM NUMBER; --局部變量 BEGIN SELECT COUNT(*) INTO V_NUM FROM EMP WHERE EMPNO=P_EMPNO; IF V_NUM=1 THEN RETURN TRUE; ELSE RETURN FALSE; END IF; END EXIST_EMP; ----------------------------- END EMP_PK; 步驟2:初始化包: SET SERVEROUTPUT ON EXECUTE EMP_PK.INIT(6000,600); 結果為: 提示信息:初始化過程已經完成! 步驟3:顯示雇員列表: EXECUTE EMP_PK.LIST_EMP; 結果為: 姓名 職務 工資 SMITH CLERK 1560 ALLEN SALESMAN 1936 WARD SALESMAN 1830 JONES MANAGER 2975 ... 雇員總人數:14 PL/SQL 過程已成功完成。 步驟4:插入一個新記錄: EXECUTE EMP_PK.INSERT_EMP(8001,'小王','CLERK',1000); 顯示結果為: 提示信息:雇員8001已插入! PL/SQL 過程已成功完成。 步驟5:通過全局變量V_EMP_COUNT查看雇員人數: BEGIN DBMS_OUTPUT.PUT_LINE(EMP_PK.V_EMP_COUNT); END; 顯示結果為: 15 PL/SQL 過程已成功完成。 步驟6:刪除新插入記錄: EXECUTE EMP_PK.DELETE_EMP(8001); 顯示結果為: 提示信息:雇員8001已刪除! PL/SQL 過程已成功完成。 再次刪除該雇員: EXECUTE EMP_PK.DELETE_EMP(8001); 結果為: 提示信息:雇員8001不存在,不能刪除! 步驟7:修改雇員工資: EXECUTE EMP_PK.CHANGE_EMP_SAL(7788,8000); 顯示結果為: 提示信息:工資超出修改范圍! PL/SQL 過程已成功完成。 步驟8:授權其他用戶調用包: 如果是另外一個用戶要使用該包,必須由包的所有者授權,下面授予STUDEN賬戶對該包的使用權: GRANT EXECUTE ON EMP_PK TO STUDENT; 每一個新的會話要為包中的公用變量開辟新的存儲空間,所以需要重新執行初始化過程。兩個會話的進程互不影響。 步驟9:其他用戶調用包。 啟動另外一個SQL*Plus,登錄STUDENT賬戶,執行以下過程: SET SERVEROUTPUT ON EXECUTE SCOTT.EMP_PK. EMP_PK.INIT(5000,700); 結果為: 提示信息:初始化過程已經完成! PL/SQL 過程已成功完成。 說明:在初始化中設置雇員的總人數和修改工資的上、下限,初始化后V_EMP_COUNT為14人,插入雇員后V_EMP_COUNT為15人。V_EMP_COUNT為公有變量,所以可以在外部程序中使用DBMS_OUTPUT.PUT_LINE輸出,引用時用EMP_PK.V_EMP_COUNT的形式,說明所屬的包。而私有變量V_MAX_SAL和V_MIN_SAL不能被外部訪問,只能通過內部過程來修改。同樣,EXIST_EMP和SHOW_MESSAGE也是私有過程,也只能在過程體內被其他模塊引用。 注意:在最后一個步驟中,因為STUDENT模式調用了SCOTT模式的包,所以包名前要增加模式名SCOTT。不同的會話對包的調用屬於不同的應用,所以需要重新進行初始化。