Oracle面向对象编程OOP

     自Oracle9i以来,Oracle就不再是单纯的关系数据库管理系统,它在关系数据库模型的基础上,添加了一系列面向对象的特性。Oracle9i就号称面向对象数据库。Oracle的对象体系遵从面向对象思想的基本特征,许多概念同C++、JAVA中类似,具有继承,重载,多态等特征,但又有自己的特点。

    为什么要引入对象模型部件可重用和简化复杂的应用程序。

     下面详细介绍下Oracle面向对象编程。

1   面向对象类型TYPE

      在PL/SQL语言中,向对象的程序设计是基于对象类型。

1.1   面向对象类型定义语法

     对象类型的定义包括对象类型头(或称为对象规范,specification)和对象类型体(body)。对象类型头包括对象类型的属性、函数和过程的声明,而对象类型体则是对象类型具体的实现,即函数和过程的实现。所以如果对象类型中只有属性,不含函数和过程的话,就只要声明对象类型头就可。
      对象类型头声明语法: 

      CREATE OR REPLACE TYPE type_name AS OBJECT(
              --属性的声明
              propertyname1     TYPE1,
              propertyname2     TYPE2,
              ...
              properynamen     TYPEn,

              --函数的声明
              member function funcname1(param1 TYPE1, ...) return TYPE11,
              static function funcname2(param1 TYPE2, ...) return TYPE22,
              ...

              --过程的声明
              member procedure  procname1(param1 TYPE1, ...),
              static procedure  procname2(param1 TYPE2, ...),
              ...
      );

    对象类型体定义语法:

      CREATE OR REPLACE TYPE BODY type_name  --No 'AS OBJECT'
      AS  --NO 'BEGIN'
          member function funcname1 return TYPE11
          IS
             //变量定义
          BEGIN
             //处理过程
             return var1;
          END funcname1;      
          static function funcname2 return TYPE22
          IS
             //变量定义
          BEGIN
             //处理过程
             return var2;
          END funcname2;
          ...
          member procedure procname1(param1 TYPE1,...)
          IS
             //变量定义
          BEGIN
             //处理过程
          END procname1;
          static procedure procname2(param1 TYPE2,...)
          IS
             //变量定义
          BEGIN
             //处理过程
          END procname2;
          ...
       END;

      说明:
         1. 方法有成员方法和静态方法,过程也有成员过程和静态过程。成员方法和成员过程通过关键字member标识,静态方法
            和静态过程通过关键字static标识。
         2. 静态方法或静态过程能直接被对象类型调用,但不能被对象实例调用(区别java)。成员方法或成员过程只能被对
            象类型的实例调用,不能直接被对象类型调用。
         3. 在静态方法或过程中不能访问对象类型的属性。
     下面定义一个NAME_TYPE对象类型,语法如下:

--对象类型头声明
CREATE OR REPLACE TYPE NAME_TYPE AS OBJECT(
    firstname      varchar2(100),
    lastname       varchar2(100),
    static function buildname(fname varchar2, lname varchar2) return varchar2,
    member function getname return varchar2,
    member procedure changefirstname(cfname varchar2),
    static procedure writename(clname varchar2)
);

--对象类型体定义
CREATE OR REPLACE TYPE BODY NAME_TYPE
AS
    static function buildname(fname varchar2, lname varchar2) return varchar2
    IS
      name varchar2(200);
    BEGIN
      name := fname||' '||lname;
      return name;
    END buildname;

    member function getname return varchar2
    IS
      name varchar2(200);
    BEGIN
      name := firstname||' '||lastname;
      return name;
    END getname;

    member procedure changefirstname(cfname varchar2)
    IS
    BEGIN
      firstname := cfname;
    END changefirstname;
    
    static procedure writename(clname varchar2)
    IS
    BEGIN
      dbms_output.put_line(clname);
    END writename;
END;

     疑问:为什么在过程中可以向对象属性直接赋值,但是在函数中却不能?

    测试代码:

declare 
  v_name VARCHAR2(100);
  v_name_t NAME_TYPE;
begin
  --对象类型直接调用静态方法writename
  dbms_output.put_line('-------OBJECT TYPE CALL STATIC PROCEDURE writename------');
  NAME_TYPE.writename('LIGUOLIANG');
  
  --对象类型直接调用静态方法buildname
  dbms_output.put_line('-------OBJECT TYPE CALL STATIC FUNCTION buildname------');
  v_name := NAME_TYPE.buildname('ZHUGE', 'LIANG');--创建对象类型实例
  dbms_output.put_line(v_name);
  
  --对象类型实例调用成员方法getname
  dbms_output.put_line('-------OBJECT TYPE INSTANCE CALL MEMBER FUNCTION buildname------');
  v_name_t := NAME_TYPE('LIU', 'BEI');
  dbms_output.put_line(v_name_t.getname);
end;
    执行结果如下:

-------OBJECT TYPE CALL STATIC PROCEDURE writename------
LIGUOLIANG
-------OBJECT TYPE CALL STATIC FUNCTION buildname------
ZHUGE LIANG
-------OBJECT TYPE INSTANCE CALL MEMBER FUNCTION buildname------
LIU BEI

1.2   对象类型使用

       对象类型可以在定义表时作为字段的类型,也可以在函数和过程中作为变量类型使用。

1.2.1  定义表

        以“人”为实体定义表t_person,并以NAME_TYPE作为name的类型,定义脚本如下:
create table T_PERSON
(
  gid         NUMBER(38) not null,
  name        NAME_TYPE,
  age         NUMBER(5),
  sex         VARCHAR2(1),
  description VARCHAR2(500),
  constraint PK_PERSON_GID primary key (gid)
);

     语句的增删改查:(访问使用对象类型的表时,如果访问了对象类型的属性,访问的属性前一定要用表的别名

declare  
  v_name NAME_TYPE;
  v_sex VARCHAR2(1);
  i_age number(3);  
begin
  --查询语句
  SELECT name, sex, age INTO v_name, v_sex, i_age FROM t_person WHERE gid = 1;
  dbms_output.put_line('NAME: '||v_name.firstname||' '||v_name.lastname);  
  
  --新增语句
  INSERT INTO t_person VALUES(3, NAME_TYPE('HE', 'MA'), 23, '1', '');
  
  --修改语句,因为要访问对象类型的属性,对象属性前必须要用表的别名  
  UPDATE t_person t SET t.name.firstname = 'QQ' WHERE t.gid = 1;
  UPDATE t_person  SET name = NAME_TYPE('XX', 'SANFENG') WHERE gid = 2;
  
  --删除语句,因为要访问对象类型的属性,对象属性前必须要用表的别名 
  DELETE FROM t_person t WHERE t.name.firstname = 'HE';
  COMMIT;
end;

1.2.2  构造方法     

     在建立对象类型时,Oracle会自动为对象类型生成相应的构造方法,方法名和对象类型名相同,该构造方法的参数列表是该对象类型的所有属性,并且构造方法用于初始化对象实例。实例化语法如下:

     type_name(param1, ...)

         举例:

         1. 向t_person表插入数据,脚本如下:

INSERT INTO t_person VALUES(2, NAME_TYPE('ZHAN', 'SAN'), 28, '1', 'GOOD PERSON');
COMMIT;
           注:向使用自定义对象类型的表插入数据,只能用构造方法。

          2. 在过程中使用,脚本如下:

declare  
  v_name   NAME_TYPE := NAME_TYPE('LI', 'LEI');
begin
  dbms_output.put_line(v_name.firstname);
  dbms_output.put_line(v_name.lastname);
end;

输出结果如下:
LI
LEI

1.2.3  对象类型比较规则--映射方法和排序方法

    在过程或方法代码分支条件,或是在SQL语句的条件中,常会用到变量或字段的比较的表达式。基本变量比较我们都比较熟悉了,而对象类型中通常包含几个属性,那么以它为类型的变量或字段大小的比较是怎么进行的呢?解决办法由两个,通过定义映射方法或是排序方法,他们都是制定比较规则的。

    映射方法和排序方法不能同时存在,且一个对象类型中最多只能有一个映射方法或排序方法。在创建映射方法后,如果用到where 或 order by 以及 <>= 等比较关系时,自动调用映射方法。

    如果对象类型中没有映射方法或排序方法,不能比较对象类型实例的大小,否则会报错。

    下面分别详细介绍:

1.2.3.1 映射方法

    映射方法通过关键字map声明,不带任何参数,有一个返回值。一个对象类型最多只有一个映射方法,如果一个对象类型没有映射方法,那么这个对象类型的实例或字段就不能比较大小。比如:

create or replace type person as object
(
    NAME varchar2(50),
    SEX varchar2(10),
    BIRTHDATE date ,
    PLACE varchar2(100),
    map member function compare2 return date
);
     
create or replace type body person 
is
map member function compare2 return date 
  is
    begin
      return self.birthdate;
    end compare2;      
end;

测试代码:

    declare
      person_one person;
      person_two person;
    begin
      person_one:=person( 'LISI' , 'NAN' , date '2008-10-20' , 'SHANGHAI' );
      person_two:=person( 'ZHANGSHAN' , 'NV' , date '2008-10-11' , 'HANZHOU' );
      if person_one > person_two then
        dbms_output.put_line(person_one.name|| ' bigger than ' ||person_two.name );
        elsif person_one < person_two then
          dbms_output.put_line(person_two.name|| ' bigger than ' ||person_one.name );
      else dbms_output.put_line( ' same ' );
      end if ;
    end ;

运行结果:

LISI bigger than ZHANGSHAN

1.2.3.2 排序方法

   排序方法通过关键字order声明,排序方法主要为了简化比较对象大小的值。比如:

create or replace type person as object
(
    NAME varchar2(50),
    SEX varchar2(10),
    BIRTHDATE date ,
    PLACE varchar2(100),
    order member function match(p_person person) return integer
);
     
create or replace type body person 
is      
order member function match(p_person person) return integer 
  is
     begin
       if self.birthdate > p_person.birthdate then
          return 1 ;
       elsif self.birthdate < p_person.birthdate then
          return - 1 ;
       else return 0 ;
       end if ;
     end match;       
end;

  测试代码:

    declare
      person_one person;
      person_two person;
    begin
      person_one:=person( 'LISI' , 'NAN' , date '2008-10-20' , 'SHANGHAI' );
      person_two:=person( 'ZHANGSHAN' , 'NV' , date '2008-10-11' , 'HANZHOU' );
      if person_one > person_two then
        dbms_output.put_line(person_one.name|| ' bigger than ' ||person_two.name );
        elsif person_one < person_two then
          dbms_output.put_line(person_two.name|| ' bigger than ' ||person_one.name );
      else dbms_output.put_line( ' same ' );
      end if ;
    end ;

   运行结果:

LISI bigger than ZHANGSHAN

1.2.4  访问对象类型实例的属性和方法

        访问规则如下:

           对象类型实例.propertyname
     对象类型实例.functionname(paramvalue1,... )
     对象类型实例.procedurename(paramvalue1, ...)

         注:引用函数或过程时,如果没有参数,函数或过程后面可以没有括号()。

    举例:

declare  
  v_name   NAME_TYPE;
  v_sex    varchar2(1);
  v_age    number(3);
begin
  select name, sex, age INTO v_name, v_sex,v_age from t_person where gid = 1;
  dbms_output.put_line('-----------修改firstname前------------');
  dbms_output.put_line(v_name.firstname);
  dbms_output.put_line(v_name.getname);--无参函数有无括号都可以
  v_name.changefirstname('WANG');
  dbms_output.put_line('-----------修改firstname后------------');
  dbms_output.put_line(v_name.firstname);
  dbms_output.put_line(v_name.getname());--无参函数有无括号都可以
end;

输出结果:

-----------修改firstname前------------
LI
LI GUOLIANG
-----------修改firstname后------------
WANG
WANG GUOLIANG

1.2.5   可变数组AS VARRAY(n) OF

     可变数组,是一种集合。一个可变数组是对象的一个集合,其中每个对象都具有相同的数据类型。可变数组的大小由创建时决定。在表中建立可变数组后,可变数组在主表中作为一个列对待。

     语法如下:

CREATE OR REPLACE TYPE varray_name AS VARRAY(n) OF type;

         语法说明:

           1.  varray_name :数组名。

           2.  n:数组的包含元素的最大个数。如果插入数组的元素大于这个上限,将会报错。

           3. type:数组的类型,可以是基本变量,如varchar2,integer等,也可以是自定义的对象类型,如上面定义的NAME_TYPE。

        4. first属性、last属性和count属性分别获得数组的首元素、末元素和总数。如有一个表

          记录变量v_student_tbl,那么v_student_tbl.first获得是表记录的首元素。在进行FOR或FORALL循环

          时比较常用。

        举例:

           一个“公司”表t_company,有一个字段包含所有员工的姓名,脚本如下:

--创建数组
CREATE OR REPLACE TYPE name_list AS VARRAY(3) OF NAME_TYPE;

--使用数组定义表
CREATE TABLE t_company
(
  gid         NUMBER(38) not null,
  name        VARCHAR2(100),
  address     VARCHAR2(100),
  employeeList   NAME_LIST,
  constraint PK_COMPANY_GID primary key (gid)
);

--向表中插入数据
INSERT INTO t_company VALUES(1, 'dakongying', 'beijing', NAME_LIST(NAME_TYPE('ZHAO', 'RUJUN'), NAME_TYPE('CHEN', 'DA')));
COMMIT; 

--查询数据
SELECT * FROM TABLE(SELECT employeeList from t_company where gid = 1);

       如果向数组赋值之前,必须初始化,初始化时,传入元素的大小必须大于等于赋值的个数,但是又要小于等于数组定义的元素上限。初始化时可以通过传入null,先分配一个空间,如下段代码所示:

declare  
  v_namelist NAME_LIST := NAME_LIST(NULL, NULL,NULL);
begin
  select name into v_namelist(1) from t_person where gid = 1;
  select name into v_namelist(2) from t_person where gid = 2;
 
  dbms_output.put_line(v_namelist(1).firstname);
  dbms_output.put_line(v_namelist(1).lastname);
  dbms_output.put_line(v_namelist(2).firstname);
  dbms_output.put_line(v_namelist(2).lastname);
  dbms_output.put_line('List length:' || v_namelist.count);--数组的count属性,返回数组元素的个数,是初始化的个数,包含NULL元素
end;

输出结果:

LI
GUOLIANG
ZHAN
SAN
List length:2

1.2.6  嵌套表AS TABLE OF

        嵌套表是表中之表,一个嵌套表是某些行的集合,它在主表中表示为其中的一列。对主表中的每一条记录,嵌套表可以包含多个行。
        语法如下:
CREATE OR REPLACE TYPE table_name AS TABLE OF type;

        语法说明:

       1.  table_name :嵌套表名。

       2. type:数组的类型,可以是基本变量,如varchar2,integer等,也可以是自定义的对象类型,如上面定义的NAME_TYPE。

       3.  first属性、last属性和count属性分别获得表记录(或称为数组)的首元素、末元素和总数。如有一个表

          记录变量v_student_tbl,那么v_student_tbl.first获得是表记录的首元素。在进行FOR或FORALL循环

          时比较常用。


1.2.7  继承-Oracle中面向对象特征

    继承父类的子类对象类型将有父类的所有属性、方法和过程。 父类型必须声明为NOT FINAL,子类型使用关键字UNDER。

    举例:

--创建父类型
CREATE TYPE animal_type AS OBJECT(
name    VARCHAR2(50),
hair    VARCHAR2(50),
foot    VARCHAR2(50)
) NOT FINAL;

--子类型继承父类型
CREATE TYPE cat_type UNDER animal_type(
paw     VARCHAR2(50)
);

   如果父类没有声明为NOT FINAL,子类在继承的时候将报错:Error: PLS-00590: attempting to create a subtype UNDER a FINAL type。

1.2.8 重写overriding

     重写就是在子类中对父类又有的方法或过程重新实现。重写关键字为overriding,在子类中把要重写的方法或过程声明和实现之前加上该关键字。

     举例:

定义头部:
CREATE OR REPLACE TYPE cat_type UNDER annimal_type
(
    paw VARCHAR2(50),
    OVERRIDING MEMBER PROCEDURE PROC_RUN
)
 
定义主体:
CREATE OR REPLACE TYPE BODY cat_type
IS
    OVERRIDING MEMBER PROCEDURE PROC_RUN
    IS
    BEGIN
      //重新实现
    END;
END;

1.2.9  对象表

      对象表是指该表的一行都是一个对象(对象类型的实例),每个对象有一个OID(object ID)。

1.2.9.1 对象表的创建

      创建对象表的语法:

CREATE TABLE table_name OF object_type;

       语法说明:
              1.table_name:对象表名称,执行创建对象表语句后,数据库中将会生成一个名字为table_name的表。
              2.object_type:对象类型,生成的表的字段和对象类型时对应的。
       举例:

CREATE TABLE t_name OF NAME_TYPE;

       执行上面语句后,数据库将生成一个t_name表,这个表就是对象表。

1.2.9.2  对象表的关联

    对象表之间没有主外键关联的概念,为了体现这层关系,oracle中用了ref对象来实现。

    下面介绍下相关操作法和函数:
       1.  ref操作符:声明引用类型。如 name ref NAME_TYPE, 变量或字段name就是引用类型,存储NAME_TYPE型对象的OID。
       2.  ref(表的别名)函数:获得对象表中对象OID值,如select ref(a) from otable a。
       3.  deref(OID)函数:通过OID找到并返回行对象表中对象。

         下面通过一个例子说明对象表直接的关联:

--员工对象类型
CREATE OR REPLACE TYPE employee AS OBJECT(
    cardId   VARCHAR2(100),
    address  VARCHAR2(100),
    sex      VARCHAR2(1),
    name     REF NAME_TYPE,  --通过REF操作符,表示该字段引用NAME_TYPE对象,该字段实际存储的时对象的OID
    age      INTEGER  
);

--创建t_employee对象表
CREATE TABLE t_employee OF employee;

--向NAME_TYPE的对象表t_name插入数据
INSERT INTO t_name VALUES('LI', 'KUI');
INSERT INTO t_name VALUES('ZHANG', 'LAN');
INSERT INTO t_name VALUES('CHEN', 'MING');
COMMIT;

--向员工表插入数据
INSERT INTO t_employee VALUES(
   '101',
   'beijing',
   '1',
   (SELECT REF(n) FROM t_name n where n.firstname = 'LI'), --通过ref(表别名)获得对象的引用
   23
);
INSERT INTO t_employee VALUES(
   '102',
   'shanghai',
   '0',
   (SELECT REF(n) FROM t_name n where n.firstname = 'ZHANG'),--通过ref(表别名)获得对象的引用
   23
);
COMMIT;

--通过声明一个引用变量,新增数据
DECLARE 
  -- 声明NAME_TYPE类型的引用
  name_ref REF NAME_TYPE;
BEGIN
  SELECT REF(n) INTO name_ref FROM t_name n where n.firstname = 'CHEN';
  INSERT INTO t_employee VALUES(
   '103',
   'chengdu',
   '1',
   name_ref,
   24
  );
  COMMIT;
END;

--查询员工的信息,使用deref来获得对象字段的值
SELECT t.cardId, t.address, deref(t.name) from t_employee t;

--更新引用类型,即赋予新的对象的OID值
UPDATE t_employee t SET name = (SELECT ref(n) FROM t_name n WHERE n.firstname = 'ZHANG') WHERE t.cardId = '103';

DELETE FROM t_employee WHERE name = (SELECT ref(n) FROM t_name n WHERE n.firstname = 'ZHANG');

你可能感兴趣的:(Oracle)