Oracle—包和包體


包的概念和組成

包是用來存儲相關程序結構的對象,它存儲於數據字典中。包由兩個分離的部分組成:包頭(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。不同的會話對包的調用屬於不同的應用,所以需要重新進行初始化。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM