IT博客汇
  • 首页
  • 精华
  • 技术
  • 设计
  • 资讯
  • 扯淡
  • 权利声明
  • 登录 注册

    Oracle学习笔记——包

    果冻想发表于 2015-09-09 14:55:57
    love 0

    Oracle中的面向对象

    写惯了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命令直接调用
    • PL/SQL中调用

    当使用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日 于呼和浩特。



沪ICP备19023445号-2号
友情链接