Oracle  11g从入门到精通(第2版) (软件开发视频大讲堂)
上QQ阅读APP看书,第一时间看更新

5.5 PL/SQL异常处理

视频讲解:光盘\TM\lx\5\PL/SQL异常处理.mp4

5.5.1 异常处理方法

在编写PL/SQL程序时,不可避免地会发生一些错误,可能是程序设计人员自己造成的,也可能是操作系统或硬件环境出错,比如出现除数为零、磁盘I/O错误等情况。对于出现的这些错误,Oracle采用异常机制来处理,异常处理代码通常放在PL/SQL的EXCEPTION代码块中。根据异常产生的机制和原理,可将Oracle系统异常分为以下两大类:

预定义异常:Oracle系统自身为用户提供了大量的、可在PL/SQL中使用的预定义异常,以便检查用户代码失败的一般原因。它们都定义在Oracle的核心PL/SQL库中,用户可以在自己的PL/SQL异常处理部分使用名称对其进行标识。对这种异常情况的处理,用户无需在程序中定义,它们由Oracle自动引发。

自定义异常:有时候可能会出现操作系统错误或机器硬件故障,这些错误Oracle系统自身无法知晓,也不能控制。例如,操作系统因病毒破坏而产生故障、磁盘损坏、网络突然中断等。另外,因业务的实际需求,程序设计人员需要自定义一些错误的业务逻辑,而PL/SQL程序在运行过程中就可能会触发到这些错误的业务逻辑。那么,对于以上这些异常情况的处理,就需要用户在程序中自定义异常,然后由Oracle自动引发。

异常的处理方法分为以下两种。

1.预定义异常处理方法

每当PL/SQL程序违反了Oracle的规则或超出系统的限制时,系统就自动地产生内部异常。每个Oracle异常都有一个号码,但异常必须按名处理。因此,PL/SQL对那些常见的异常预定义了异常名。

2.预定义异常和用户自定义异常处理方法

异常声明:

用户定义异常包括预定义异常和用户自定义异常,用户定义的异常只能在PL/SQL块的声明部分进行声明。声明方式与变量声明类似。

抛出异常:

用户定义的异常使用RAISE语句显式地提出。

为内部异常命名:

在PL/SQL中,必须使用OTHERS处理程序或用伪命令EXCEPTION_INIT来处理未命名的内部异常。EXCEPTION_INIT的作用是告诉编译程序将一个异常名与一个Oracle错误号码联系起来。因此,用户就可以按名称引用任何内部异常,并为它编写一个特定的处理程序。

注意

异常是一种状态而不是一个对象,因此,异常名不能出现在赋值语句或SQL语句中。PRAGMA EXCEPTION_INIT的作用是将一个异常名与一个Oracle错误号码联系起来。因此,用户就可以按名称引用任何内部异常,并为它编写一个特定的处理程序。

5.5.2 异常处理语法

1.声明异常

语法:

        exception_name EXCEPTION;

其中,exception_name为用户定义的异常名。

2.为内部异常命名

        PRAGE EXCEPTION_INIT(exception_name, ORA_errornumber);

其中,ORA_errornumber为用户定义的Oracle错误号。

3.异常定义

代码如下所示。

        DECLARE
            exceprion_name EXCEPTION;
        BEGIN
            IF condition THEN
                RAISE exception_name;
            END IF;
            EXCEPTION
                WHEN exception_name THEN
                Statement;
        END;

4.异常处理

代码如下所示。

        SET SERVEROUTPUT ON  --将输出流开关打开
        EXCEPTION
            WHEN exception1 THEN
                statement1
            WHEN exception2 THEN
                statement2
            ……
            WHEN OTHERS THEN
                statement3

5.使用SQLCODE和SQLERRM函数定义提示信息

        DBMS_OUTPUT.PUT_LINE(’错误号:'||SQLCODE);
        DBMS_OUTPUT.PUT_LINE(’错误号:'||SQLERRM);

5.5.3 预定义异常

当PL/SQL程序违反了Oracle系统内部规定的设计规范时,就会自动引发一个预定义的异常,例如,当除数为零时,就会引发ZERO_DIVIED异常。Oracle系统常见的预定义异常标识符如下:

ACCESS_INTO_NULL:该异常对应于ORA-06530错误。为了引用对象属性,必须首先初始化对象。当直接引用未初始化的对象属性时,会触发该异常。

CASE_NOT_FOUND:该异常应用于ORA-06592错误。当CASE语句的WHEN子句没有包含必须条件分支或者ELSE子句时,会触发该异常。

COLLECTION_IS_NULL:该异常应用于ORA-06531错误。在给嵌套表变量或者VARRAY变量赋值之前,必须首先初始化集合变量。如果没有初始化集合变量,会触发该异常。

CURSOR_ALREADY_OPEN:该异常应用于ORA-06511错误。当在已打开游标上执行OPEN操作时,会触发该异常。

INVALID_CURSOR:该异常应用于ORA-01001错误。当视图从未打开游标提取数据,或者关闭未打开游标时,会触发该异常。

INVALID_NUMBER:该异常应用于ORA-01722错误。当内嵌SQL语句不能将字符转变成数字时,会触发该异常。

LOGIN_DENIED:该异常应用于ORA-01017错误。当连接到Oracle数据库时,如果提供了不正确的用户名或者口令,会触发该异常。

NO_DATA_FOUND:该异常应用于ORA-01403错误。当执行SELECT INTO未返回行,或者引用了未初始化的PL/SQL表元素时,会触发该异常。

NOT_LOGGED_ON:该异常应用于ORA-01012错误。如果没有连接到Oracle数据库,当执行内嵌SQL语句时,会触发该异常。

PROGRAM_ERROR:该异常应用于ORA-06501错误。如果出现该错误,则表示存在PL/SQL内部问题,在这种情况下需要重新安装数据字典视图和PL/SQL包。

ROWTYPE_MISMATCH:该异常应用于ORA-016504错误。当执行赋值操作时,如果宿主变量和游标变量具有不兼容的返回类型,会触发该异常。

SELF_IS_NULL:该异常应用于ORA-30625错误。当使用对象类型时,如果在NULL实例上调用成员方法,会触发该异常。

STORAGE_ERROR:该异常应用于ORA-06500错误。当执行PL/SQL块时,如果超出内存空间或者内存被破坏,会触发该异常。

SUBSCRIPT_BEYOND_COUNT:该异常应用于ORA-06533错误。当使用嵌套表或者VARRAY元素时,如果下标超出了嵌套表或者VARRAY元素的范围,会触发该异常。

SUBSCRIPT_OUTSIDE_LIMIT:该异常应用于ORA-06532错误。当使用嵌套表或者VARRAY元素时,如果元素下标为负值,会触发该异常。

SYS_INVALID_ROWID:该异常应用于ORA-01410错误。当将字符串转变为ROWID时,如果使用了无效字符串,会触发该异常。

TIMEOUT_ON_RESOURCE:该异常应用于ORA-00051错误。当等待资源时如果出现超时错误,会触发该异常。

TOO_MANY_ROWS:该异常应用于ORA-01422错误。当执行SELECT INTO语句时,如果返回超过一行,会触发该异常。

VALUE_ERROR:该异常应用于ORA-06502错误。当执行赋值操作时,如果变量长度不足以容纳实际数据,会触发该异常。

ZERO_DIVIDE:该异常应用于ORA-01476错误。如果用数字值除以0,会触发该异常。

下面通过一个实例来说明如何使用系统预定义异常。

【例5.34】 使用SELECT INTO语句检索emp表中部门编号为10的雇员记录信息,然后使用“too_many_rows”预定义异常捕获错误信息并输出,代码如下(实例位置:光盘\TM\sl\5\17)

        SQL> set serveroutput on
        SQL> declare
          2   var_empno number;                    --定义变量,存储雇员编号
          3   var_ename varchar2(50);              --定义变量,存储雇员名称
          4  begin
          5   select empno, ename into var_empno, var_ename
          6   from emp
          7   where deptno=10;                     --检索部门编号为10的雇员信息
          8   if sql%found then                    --若检索成功,则输出雇员信息
          9     dbms_output.put_line(’雇员编号:'||var_empno||';雇员名称’||var_ename);
         10   end if;
         11  exception                             --捕获异常
         12   when too_many_rows then              --若SELECT INTO语句的返回记录超过一行
         13     dbms_output.put_line(’返回记录超过一行’);
         14   when no_data_found then              --若SELECT INTO语句的返回记录为0行
         15     dbms_output.put_line(’无数据记录’);
         16  end;
         17  /

本例运行结果如图5.18所示。

图5.18 使用too_many_rows异常

在上面的例子中,由于部门编号为10的员工记录数大于1,所以SELECT INTO语句的返回行数就要超过一行,由于Oracle系统内部规定不允许该语句的返回行数超过一行,所以必然会引发异常,即引发too_many_rows系统预定义异常。

5.5.4 自定义异常

Oracle系统内部的预定义异常仅仅20个左右,而实际程序运行过程中可能会产生几千种异常情况,为此Oracle经常使用错误编号和相关描述输出异常信息。另外,程序设计人员可以根据实际的业务需求定义一些特殊异常,这样Oracle的自定义异常就可以分为错误编号异常和业务逻辑异常两种。

1.错误编号异常

错误编号异常是指在Oracle系统发生错误时,系统会显示错误号和相关描述信息的异常。虽然直接使用错误编号也可以完成异常处理,但错误编号较为抽象,不易于用户理解和记忆,对于这种异常,首先在PL/SQL块的声明部分(DECLARE部分)使用EXCEPTION类型定义一个异常变量名,然后使用语句PRAGMA EXCEPTION_INIT为“错误编号”关联“这个异常变量名”,接下来就可以像对待系统预定义异常一样处理了。

下面通过一个具体的实例来演示如何为Oracle系统的“错误编号”做自定义异常处理。首先我们向dept表中插入一条部门编号为10的记录(事先查询过,部门编号10已经存在于dept表中,并且部门编号为dept表的唯一主键),然后执行INSERT语句,得到如图5.19所示的运行结果。

图5.19 因主键值重复而显示的错误编号

从图5.19所示的运行结果中可以看到,程序执行中断而崩溃掉了,并显示错误信息为“ORA-00001”—即错误编号为“00001”,那么对于Oracle捕获到的这个异常可以通过如下实例来解决。

【例5.35】 定义错误编号为“00001”的异常变量,然后向dept表中插入一条能够“违反唯一约束条件”的记录,最后在exception代码体中输出异常提示信息,代码如下(实例位置:光盘\TM\sl\5\18)

        SQL> set serveroutput on
        SQL> declare
          2   primary_iterant exception;                       --定义一个异常变量
          3   pragma exception_init(primary_iterant, -00001);  --关联错误号和异常变量名
          4  begin
          5   /*向dept表中插入一条与已有主键值重复的记录,以便引发异常*/
          6   insert into dept values(10, ’软件开发部’, ’深圳’);
          7  exception
          8   when primary_iterant then                        --若Oracle捕获到的异常为-0001异常
          9     dbms_output.put_line(’主键不允许重复!');      --输出异常描述信息
         10  end;
         11  /

本例运行结果如图5.20所示。

图5.20 定义主键值重复的异常

通过运行结果可以看到,使用异常处理机制,可以防止Oracle系统因引发异常而导致程序崩溃,使程序有机会自动纠正错误,而且自定义异常容易理解和记忆,方便用户的使用。

2.业务逻辑异常

在实际的应用中,程序开发人员可以根据具体的业务逻辑规则自定义一个异常。这样,当用户操作违反业务逻辑规则时,就引发一个自定义异常,从而中断程序的正常执行并转到自定义的异常处理部分。

无论是预定义异常,还是错误编号异常,都是由Oracle系统判断的错误,但业务逻辑异常是Oracle系统本身无法知道的,这样就需要有一个引发异常的机制,引发业务逻辑异常通常使用RAISE语句来实现。当引发一个异常时,控制就会转到EXCEPTION异常处理部分执行异常处理语句。业务逻辑异常首先要在DECLARE部分使用EXCEPTION类型声明一个异常变量,然后在BEGIN部分根据一定的业务逻辑规则执行RAISE语句(在RAISE关键字后面跟着异常变量名),最后在EXCEPTION部分编写异常处理语句。下面通过一个实例来演示如何定义和引发“业务逻辑异常”。

【例5.36】 自定义一个异常变量,在向dept表中插入数据时,若判断loc字段的值为null,则使用RAISE语句引发异常,并将程序的执行流程转入到EXCEPTION部分进行处理,代码如下(实例位置:光盘\TM\sl\5\19)

        SQL> set serveroutput on
        SQL> declare
          2   null_exception exception;                    --声明一个exception类型的异常变量
          3   dept_row dept%rowtype;                       --声明rowtype类型的变量dept_row
          4  begin
          5   dept_row.deptno:=66;                         --给部门编号变量赋值
          6   dept_row.dname:=’公关部’;                   --给部门名称变量赋值
          7   insert into dept
          8   values(dept_row.deptno, dept_row.dname, dept_row.loc); --向dept表中插入一条记录
          9   if dept_row.loc is null then                  --如果判断“loc”变量的值为null
         10     raise null_exception;                       --引发null异常,程序转入exception部分
         11   end if;
         12  exception
         13   when null_exception then                      --当raise引发的异常是null_exception时
         14   dbms_output.put_line('loc字段的值不许为null'); --输出异常提示信息
         15   rollback;                                     --回滚插入的数据记录
         16  end;
         17  /

本例运行结果如图5.21所示。

图5.21 业务逻辑异常

说明

使用desc命令查看dept表的设计情况,可以看到loc字段允许为null,但实际应用中loc字段的值(部门位置)可能会被要求必须填写,这样程序设计人员就可以通过自定义业务逻辑异常来限制loc字段的值不许为空。