DM 通过类类型在 DMSQL 程序中实现面向对象编程的支持。类将结构化的数据及对其进行操作的过程或函数封装在一起。允许用户根据现实世界的对象建模,而不必再将其抽象成关系数据。
DM 的类类型分为普通类类型和 JAVA CLASS 类型。DM 文档中的示例除了特别声明使用的是 JAVA CLASS 类型,否则使用的都是普通类类型。
12.1 普通 CLASS 类型
DM 的类的定义分为类头和类体两部分,类头完成类的声明;类体完成类的实现。
类中可以包括以下内容:
1. 类型定义
在类中可以定义游标、异常、记录类型、数组类型、以及内存索引表等数据类型,在类的声明及实现中可以使用这些数据类型;类的声明中不能声明游标和异常,但是实现中可以定义和使用。
2. 属性
类中的成员变量,数据类型可以是标准的数据类型,可以是在类中自定义的特殊数据类型。
3. 成员方法
类中的函数或过程,在类头中进行声明;其实现在类体中完成;
成员方法及后文的构造函数包含一个隐含参数,即自身对象,在方法实现中可以通过 this 或 self 来访问自身对象,self 等价于 this。如果不存在重名问题,也可以直接使用对象的属性和方法。this 和 self 只能在包或对象脚本中调用。
4. 构造函数
构造函数是类内定义及实现的一种特殊的函数,这类函数用于实例化类的对象,构造函数满足以下条件:
- 函数名和类名相同;
- 函数返回值类型为自身类。
构造函数存在以下的约束:
- 系统为每个类提供两个默认的构造函数,分别为 0 参的构造函数和全参的构造函数;
- 0 参构造函数的参数个数为 0,实例的对象内所有的属性初始化值为 NULL;
- 全参构造函数的参数个数及类型和类内属性的个数及属性相同,按照属性的顺序依次读取参数的值并给属性赋值;
- 用户可以自定义构造函数,一个类可以有多个构造函数,但每个构造函数的参数个数必须不同;
- 如果用户自定义了 0 个参数、或参数个数同属性个数相同的构造函数,则会覆盖相应的默认构造函数。
下面从类的声明、类的实现、类的删除、类体的删除和类的使用几部分来详细介绍类类型的实现过程。
12.1.1 声明类
类的声明在类头中完成。类头定义通过 CREATE CLASS 语句来完成,其语法为:
语法格式
CREATE [OR REPLACE] CLASS [IF NOT EXISTS] [<模式名>.]<类名> [WITH ENCRYPTION] [UNDER [<模式名>.]<父类名>] [[NOT] FINAL] [[NOT] INSTANTIABLE] [AUTHID DEFINER | AUTHID CURRENT_USER] AS|IS <类内声明列表> END [<类名>];
<类内声明列表> ::= <类内声明>;{<类内声明>;}
<类内声明> ::= <变量定义>|<过程定义>|<函数定义>|<类型声明>
<变量定义> ::= <变量名列表> <数据类型> [<默认值定义>]
<过程定义> ::= [<方法继承属性>][STATIC|MEMBER] PROCEDURE <过程名> <参数列表>
<函数定义> ::= [<方法继承属性>] [MAP] [STATIC|MEMBER] FUNCTION <函数名><参数列表> RETURN <返回值数据类型>[DETERMINISTIC][PIPELINED]
<方法继承属性> ::= <重载属性> | <final属性> | <重载属性> <final属性>
<重载属性> ::= [NOT] OVERRIDING
<final属性> ::= FINAL | NOT FINAL | INSTANTIABLE | NOT INSTANTIABLE
<类型声明> ::= TYPE <类型名称> IS <数据类型>
图例
声明类
使用说明
-
类的名称不能与系统创建的模式名称相同;
-
类中元素可以以任意顺序出现,其中的对象必须在引用之前被声明;
-
过程和函数的声明都是前向声明,类声明中不包括任何实现代码;
-
支持对象静态方法声明与调用。可以在 PROCEDURE/FUNCTION 关键字前添加 static 保留字,以此指明方法为静态方法。静态方法只能以对象名为前缀调用,而不能在对象实例中调用;
-
支持对象成员方法声明与调用。可以在 PROCEDURE/FUNCTION 关键字前添加 MEMBER 以指明方法为成员方法。MEMBER 与 STATIC 不能同时使用,非 STATIC 类型的非构造函数方法默认为成员方法。MAP 表示将对象类型的实例映射为标量数值,只能用于成员类型的 FUNCTION;
-
关于类继承,有以下使用限制:
- 类定义默认为 FINAL,表示该对象类型不能被继承,定义父类时必须指定 NOT FINAL 选项;
- 定义子类时必须指定 UNDER 选项;
- NOT INSTANTIABLE 对象不能为 FINAL;
- NOT INSTANTIABLE 对象不能实例化,但是可以用其子类赋值;
- 对象实例化时,必须对父类和子类的成员变量都赋值,且从父类到子类逐个赋值;
- 不支持对象的循环继承;
- 不支持对象的多继承,即一个类有多个父类;
- 不支持父类和子类包含同名变量;
- 父类和子类可以同名同参,此时子类必须指定 OVERRIDING;
- 方法默认为 NOT OVERRIDING,OVERRIDING 不能与 static 一起使用;
- 父类和子类支持同名不同参(参数个数不同、参数个数相同但类型不同)的方法;
- 同名且参数个数相同但类型不同时,根据参数类型选择使用的方法;
- 方法默认为 INSTANTIABLE,如果声明为 NOT INSTANTIABLE,则不能与 FINAL、STATIC 一起使用;
- 如果父类有多个 NOT INSTANTIABLE 方法,子类可以只部分重写,但此时子类必须定义为 NOT FINAL NOT INSTANTIABLE;
- NOT INSTANTIABLE 方法不能具有主体;
- 方法默认为 NOT FINAL,如果声明为 FINAL,则不能被子类重写;
- 子类可以赋值给父类;
- 如果父类对应的实例是子类或者子类的孩子,则该父类可以赋值给子类;
- 可以用 INSTANTIABLE 子类对 NOT INSTANTIABLE 父类进行赋值;
- 子类实例赋值给父类后,调用时使用的是父类方法而不是子类方法;
- 支持使用 as 语句转换为父类。
权限
1、使用该语句的用户必须是 DBA 或具有 CREATE PACKAGE 数据库权限的用户;
2、可以用关键字 AUTHID DEFINER |AUTHID CURRENT_USER 指定类的调用者权限,若为 DEFINER,则采用类定义者权限,若为 CURRENT_USER 则为当前用户权限,默认为类定义者权限。
12.1.2 实现类
类的实现通过类体完成。类体的定义通过 CREATE CLASS BODY 语句来完成,其语法为:
语法格式
CREATE [OR REPLACE] CLASS BODY [<模式名>.]<类名> [WITH ENCRYPTION] AS|IS <类体部分> END [类名];
<类体部分> ::= <过程/函数列表> [<初始化代码>]
<过程/函数列表> ::= <过程实现|函数实现>{,<过程实现|函数实现> }
<过程实现> ::= [<方法继承属性>][STATIC|MEMBER]PROCEDURE <过程名> <参数列表> AS|IS BEGIN <实现体> END [过程名]
<函数实现> ::= [<方法继承属性>][MAP] [STATIC|MEMBER]FUNCTION <函数名><参数列表> RETURN <返回值数据类型>[DETERMINISTIC] [PIPELINED] AS|IS BEGIN <实现体> END [函数名]
<方法继承属性> ::= <重载属性> | <final属性> | <重载属性> <final属性>
<重载属性> ::= [NOT] OVERRIDING
<final属性> ::= FINAL | NOT FINAL | INSTANTIABLE | NOT INSTANTIABLE
<初始化代码> ::= [[<说明部分>]BEGIN<执行部分>[<异常处理部分>]]
<说明部分> ::=[DECLARE]<说明定义>{<说明定义>}
<说明定义>::=<变量说明>|<异常变量说明>|<子游标定义>|<子过程定义>|<子函数定义>
<变量说明>::=<变量名>{,<变量名>}<变量类型>[DEFAULT|ASSIGN|:=<表达式>];
<变量类型>::=<DMSQL程序类型> | [<模式名>.]<表名>.<列名>%TYPE | [<模式名>.]<表名>%ROWTYPE | <记录类型>
<记录类型>::= RECORD(<变量名> <DMSQL程序类型>{,<变量名> <DMSQL程序类型>})
<异常变量说明>::=<异常变量名>EXCEPTION[FOR<错误号>]
<子游标定义>::=CURSOR <游标名> [FOR<查询表达式>|<连接表>]
<子过程定义>::=PROCEDURE<过程名>[(<参数列>)] IS|AS <模块体>
<子函数定义>::=FUNCTION<函数名>[(<参数列>)]RETURN<返回数据类型> [PIPELINED] IS|AS <模块体>
<执行部分>::=<SQL过程语句序列>{;< SQL过程语句序列>}
<SQL过程语句序列>::=[<标号说明>]<SQL过程语句>;
<标号说明>::=<<<标号名>>>
<SQL过程语句>::=<SQL语句>|<SQL控制语句>
<异常处理部分>::=EXCEPTION<异常处理语句>{;<异常处理语句>};
<异常处理语句>::= WHEN <异常名> THEN < SQL过程语句序列>
图例
实现类
使用说明
- 类声明中定义的对象对于类体而言都是可见的,不需要声明就可以直接引用。这些对象包括变量、游标、异常定义和类型定义;
- 类体中的过程、函数定义必须和类声明中的声明完全相同。包括过程的名字、参数定义列表的参数名和数据类型定义;
- 若类声明中的过程、函数声明中的参数包含默认值,则允许类体中的过程、函数定义中的参数忽略该默认值,若未忽略则默认值必须与类声明中保持一致;若类声明中的过程、函数声明中的参数不包含默认值,则类体中的过程、函数定义中的参数也不能包含默认值;
- 类中可以有重名的成员方法,要求其参数定义列表各不相同。系统会根据用户的调用情况进行重载(Overload);
- 声明类与实现类时,对于确定性函数的指定逻辑与包内函数相同。目前不支持类的确定性函数在函数索引中使用。
权限
使用该语句的用户必须是 DBA 或该类对象的拥有者且具有 CREATE PACKAGE 数据库权限的用户。
完整的类头、类体的创建如下所示:
//类头创建
create class mycls
as
type rec_type is record (c1 int, c2 int); //类型声明
id int; //成员变量
r rec_type; //成员变量
function f1(a int, b int) return rec_type; //成员函数
function mycls(id int , r_c1 int, r_c2 int) return mycls;
--用户自定义构造函数
end;
/
//类体创建
create or replace class body mycls
as
function f1(a int, b int) return rec_type
as
begin
r.c1 = a;
r.c2 = b;
return r;
end;
function mycls(id int, r_c1 int, r_c2 int) return mycls
as
begin
this.id = id; //可以使用this.来访问自身的成员
r.c1 = r_c1; //this也可以省略
r.c2 = r_c2;
return this; //使用return this 返回本对象
end;
end;
/
12.1.3 重编译类
重新对类进行编译,如果重新编译失败,则将类置为禁止状态。
重编功能主要用于检验类的正确性。
语法格式
ALTER CLASS [<模式名>.]<类名> COMPILE [DEBUG];
参数
1.< 模式名 > 指明被重编译的类所属的模式;
2.< 类名 > 指明被重编译的类的名字;
3.[DEBUG] 可忽略。
图例
重编译类
权限
执行该操作的用户必须是类的创建者,或者具有 DBA 权限。
举例说明
例 重新编译类
ALTER CLASS mycls COMPILE;
12.1.4 删除类
类的删除分为两种方式:一是类头的删除,删除类头则会顺带将类体一起删除;另外一种是类体的删除,这种方式只能删除类体,类头依然存在。
12.1.4.1 删除类头
类的删除通过 DROP CLASS 完成,即类头的删除。删除类头的同时会一并删除类体。
语法格式
DROP CLASS [IF EXISTS] [<模式名>.]<类名>[RESTRICT | CASCADE];
使用说明
- 删除不存在的类头会报错。若指定 IF EXISTS 关键字,删除不存在的类头,不会报错;
- 如果被删除的类不属于当前模式,必须在语句中指明模式名;
- 如果一个类的声明被删除,那么对应的类体被自动删除。
权限
执行该操作的用户必须是 DBA,或者是该类的拥有者且具有 DROP PACKAGE 权限。
12.1.4.2 删除类体
从数据库中删除一个类的实现主体对象。
语法格式
DROP CLASS BODY [IF EXISTS] [<模式名>.]<类名>[RESTRICT | CASCADE];
使用说明
- 删除不存在的类体会报错。若指定 IF EXISTS 关键字,删除不存在的类体,不会报错;
- 如果被删除的类不属于当前模式,必须在语句中指明模式名。
权限
执行该操作的用户必须是 DBA,或者是该类的拥有者且具有 DROP PACKAGE 权限。
12.1.5 应用实例
下面列举一个简单的应用实例。在列对象上如何使用普通 CLASS。
- 变量对象的应用实例
declare
type ex_rec_t is record (a int, b int); //使用一个同结构的类型代替类定义的类型
rec ex_rec_t;
o1 mycls;
o2 mycls;
begin
o1 = new mycls(1,2,3);
o2 = o1; //对象引用
rec = o2.r; //变量对象的成员变量访问
print rec.a; print rec.b;
rec = o1.f1(4,5); //成员函数调用
print rec.a; print rec.b;
print o1.id; //成员变量访问
end;
- 列对象的应用实例
表的创建。
Create table tt1(c1 int, c2 mycls);
列对象的创建--插入数据。
Insert into tt1 values(1, mycls(1,2,3));
列对象的复制及访问。
Declare
o mycls;
id int;
begin
select top 1 c2 into o from tt1; //列对象的复制
select top 1 c2.id into id from tt1; //列对象成员的访问
end;
- 类继承的应用实例
CREATE OR REPLACE CLASS cls01 NOT FINAL IS
name VARCHAR2(10);
MEMBER FUNCTION get_info RETURN VARCHAR2;
END;
CREATE OR REPLACE CLASS cls02 UNDER cls01 IS
ID INT;
OVERRIDING MEMBER FUNCTION get_info RETURN VARCHAR2;
END;
12.2 JAVA CLASS 类型
JAVA 类的定义类似 JAVA 语言语法,类中可定义。
JAVA 类中可以包括以下内容:
1. 类型定义
在类中可以定义游标、异常,可以声明记录类型、数组类型、结构体类型以及内存索引表等数据类型变量。
2. 属性
类中的成员变量,数据类型可以是标准的数据类型,可以是在类外自定义的特殊数据类型。
3. 成员方法
JAVA 类中的成员方法及后文的构造函数包含一个隐含参数,即自身对象,在方法实现中可以通过 this 或 self 来访问自身对象,self 等价于 this。如果不存在重名问题,也可以直接使用对象的属性和方法。
4. 构造函数
构造函数是类内定义及实现的一种特殊的函数,这类函数用于实例化类的对象,构造函数满足以下条件:
- 函数名和类名相同;
- 函数没有返回值类型。
构造函数存在以下的约束:
- 系统为每个类提供两个默认的构造函数,分别为 0 参的构造函数和全参的构造函数;
- 0 参构造函数的参数个数为 0,实例的对象内所有的属性初始化值为 NULL;
- 全参构造函数的参数个数及类型和类内属性的个数及属性相同,按照属性的顺序依次读取参数的值并给属性赋值;
- 用户可以自定义构造函数,一个类可以有多个构造函数,但每个构造函数的参数个数必须不同;
- 如果用户自定义了 0 个参数、或参数个数同属性个数相同的构造函数,则会覆盖相应的默认构造函数。
12.2.1 定义 JAVA 类
定义通过 CREATE JAVA CLASS 语句来完成,其语法为:
语法格式
CREATE [OR REPLACE] JAVA [PUBLIC] [ABSTRACT] [FINAL] CLASS <类名>
[EXTENDS [<模式名>.]<父类名>] {<类内定义部分> }
<类内定义部分> ::= <类内定义>;{<类内定义>;}
<类内定义> ::= PUBLIC|PRIVATE <定义子句>
<定义子句> ::= <变量定义>|<方法定义>
<变量定义> ::= <变量属性> <数据类型><变量名列表> [<默认值定义>]
<变量属性> ::= [STATIC] <final属性>
<方法定义> ::= [PUBLIC|PRIVATE] [<方法继承属性>] [STATIC] <返回类型> <函数名><参数列表> {<实现体>}
<方法继承属性> ::= <重载属性> | <FINAL属性> | <ABSTRACT属性>
<ABSTRACT属性> ::= ABSTRACT
<FINAL属性> ::= FINAL
<重载属性> ::= OVERRIDE
图例
定义 JAVA 类
< 类内定义部分 >(class_inner_define_part)
使用说明
- 类的名称不能与系统创建的模式名称相同;
- 类中元素可以以任意顺序出现,其中的对象必须在引用之前被声明;
- 支持对象静态方法声明与调用。可以在方法前添加 static 保留字,以此指明方法为静态方法。静态方法只能以对象名为前缀调用,而不能在对象实例中调用;
- 支持对象成员方法声明与调用。非 STATIC 类型的非构造函数方法默认为成员方法。成员方法调用时,需要先实例化,实例化参数值缺省为 null;
- 变量定义还包括游标、异常定义。使用游标时需要在 OPEN、FETCH 及 CLOSE 后使用“CURSOR”关键字;
- 方法属性是 PUBLIC,则访问类时可以访问,如果是 PRIVATE 属性,则访问类时不可以访问该方法;
- 关于 JAVA 类继承,有以下使用限制:
- JAVA CLASS 定义默认可继承,FINAL 表示该类不能被继承;
- 定义子类时必须指定 EXTENDS 选项;
- ABSTRACT 对象不能为 FINAL;
- ABSTRACT 对象不能实例化,但是可以用其子类赋值;
- 子类对象实例化时,必须对父类和子类的成员变量都赋值,且从父类到子类逐个赋值;
- 不支持对象的循环继承;
- 不支持对象的多继承,即一个类只能有一个父类;
- 不支持父类和子类包含同名变量;
- 父类和子类可以同名同参,此时子类必须指定 OVERRIDE;
- 方法默认为 NOT OVERRIDING,OVERRIDING 不能与 static 一起使用;
- 父类和子类支持同名不同参(参数个数不同、参数个数相同但类型不同)的方法;
- 同名且参数个数相同但类型不同时,根据参数类型选择使用的方法;
- 方法如果声明为 ABSTRACT,则不能与 FINAL、STATIC 一起使用;
- 如果父类有多个 ABSTRACT 方法,子类可以只部分重写,但此时子类必须定义为 ABSTRACT;
- ABSTRACT 方法不能具有主体;
- 方法默认为可继承,如果声明为 FINAL,则不能被子类重写;
- 子类可以赋值给父类;
- 如果父类对应的实例是子类或者子类的孩子,则该父类可以赋值给子类;
- 可以用 ABSTRACT 子类对非 ABSTRACT 父类进行赋值;
- 子类实例赋值给父类后,调用时使用的是父类方法而不是子类方法;
- 支持使用 super 无参方法转换为父类引用;
- 支持使用 this()调用该类构造函数,super()调用父类构造函数;
- 子类必须有新增成员或方法,不能完全为空。
权限
使用该语句的用户必须是 DBA 或具有 CREATE PACKAGE 数据库权限的用户。
12.2.2 重编译 JAVA 类
重新对 JAVA 类进行编译,如果重新编译失败,则将 JAVA 类置为禁止状态。
重编功能主要用于检验 JAVA 类的正确性。
语法格式
ALTER JAVA CLASS [<模式名>.]<JAVA类名> COMPILE [DEBUG];
参数
1.< 模式名 > 指明被重编译的 JAVA 类所属的模式;
2.<JAVA 类名 > 指明被重编译的 JAVA 类的名字;
3.[DEBUG] 可忽略。
图例
重编译 JAVA 类
权限
执行该操作的用户必须是 JAVA 类的创建者,或者具有 DBA 权限。
12.2.3 删除 JAVA 类
JAVA 类的删除通过 DROP CLASS 语句完成。
语法格式
DROP CLASS [IF EXISTS] [<模式名>.] <JAVA类名> [RESTRICT | CASCADE];
参数
1.< 模式名 > 指明待删除的 JAVA 类所属的模式,可忽略;
2.<JAVA 类名 > 指明待删除的 JAVA 类的名字。
图例
删除 JAVA 类
使用说明
删除不存在的 JAVA 类会报错。若指定 IF EXISTS 关键字,删除不存在的 JAVA 类,不会报错。
权限
执行该操作的用户必须是 DBA,或者是该类的拥有者且具有 DROP PACKAGE 权限。
12.2.4 应用实例
下面列举一个简单的应用实例。在列对象上如何使用 JAVA CLASS。
1.创建 JAVA CLASS。
create or replace java class jcls
{
int a;
public static int testAdd2(int a, int b) { //此处创建的是静态STATIC方法
return a + b;
}
public int testAdd3(int a, int b, int c) { //此处创建的是成员方法
return a + b +c;
}
}
- 在列对象中使用 JAVA CLASS。
create table tt2(c1 int, c2 jcls);
insert into tt2 values(jcls.testadd2(1,2),jcls(1)); //静态方法调用
insert into tt2 values(jcls().testadd3(1,2,3),jcls(2)); //成员方法调用之前必须实例化
12.3 使用规则
类类型同普通的数据类型一样,可以作为表中列的数据类型,DMSQL 程序语句块中变量的数据类型或过程及函数参数的数据类型。
1. 作为表中列类型或其他类成员变量属性的类不能被修改,删除时需要指定 CASCADE 级联删除
类中定义的数据类型,其名称只在类的声明及实现中有效。如果类内的函数的参数或返回值是类内的数据类型,或是进行类内成员变量的复制,需要在 DMSQL 程序中定义一个结构与之相同的类型。
根据类使用方式的不同,对象可分为变量对象及列对象。变量对象指的是在 DMSQL 程序语句块中声明的类类型的变量;列对象指的是在表中类类型的列。变量对象可以修改其属性的值而列对象不能。
2. 变量对象的实例化
类的实例化通过 NEW 表达式调用构造函数完成。
3. 变量对象的引用
通过‘=’进行的类类型变量之间的赋值所进行的是对象的引用,并没有复制一个新的对象。
4. 变量对象属性访问
可以通过如下方式进行属性的访问。
<对象名>.<属性名>
5. 变量对象成员方法调用
成员方法的调用通过以下方式调用:
<对象名>.<成员方法名>(<参数>{,<参数>})
如果函数内修改了对象内属性的值,则该修改生效。
6. 列对象的插入
列对象的创建是通过 INSERT 语句向表中插入数据完成,插入语句中的值是变量对象,插入后存储在表中的数据即为列对象。
7. 列对象的复制
存储在表中的对象不允许对对象中成员变量的修改,通过 into 查询或’=’进行的列到变量的赋值所进行的是对象的赋值,生成了一个与列对象数据一样的副本,在该副本上进行的修改不会影响表中列对象的值。
8. 列对象的属性访问
通过如下方式进行属性的访问:
<列名>.<属性名>
9. 列对象的方法调用
<列名>.<成员方法名>(<参数>{,<参数>})
列对象方法调用过程中对类型内属性的修改,都是在列对象的副本上进行的,不会影响列对象的值。