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字段的值不许为空。