--程序员在开发的时候,经常天真的认为这个世界是完美的,用户如同自己般聪明,总能按照自己设想的方式
--操作系统输入数据。但残酷的事实告诉我们,这是不可能的事情,用户总会跟我们相反的方式操作系统
--于是,无数的问题砸向我们。
--那么怎么办呢?
---PL/SQL提供了强大、灵活的错误捕获和处理方法,这节课我们来领略她的风采
--什么是异常?先看一个例子
DECLARE V_DIV NUMBER := 20;V_NUM NUMBER := 0; BEGIN DBMS_OUTPUT.put_line(V_DIV/V_NUM);END ; /
--在plsql中,任何类型的错误都可按程序异常统一对待,可能是
--系统产生的错误,比如内存溢出,或者索引出现重复值
--用户动作导致的错误
--应用程序向用户发出的警告
--异常处理单元
DECLARE V_DIV NUMBER := 20;V_NUM NUMBER := 0; BEGIN DBMS_OUTPUT.put_line(V_DIV/V_NUM);exception when standard.zero_divide then DBMS_OUTPUT.put_line('除数为零');--异常处理单元 END ; /
--异常分类
--系统定义异常:oracle定义的异常,在PLSQL运行时引擎发现某个错误跑出来的异常,比如NO_DATA_FOUND
--程序员自定义异常:程序员定义的异常,专门针对当前的应用程序。
--可使用EXCEPTION N_INIT指定错误名称,或者使用RAISE_APPILICATION_ERROR给错误指定一个数字和描述
--异常的定义
--声明有名异常
exception_name EXCEPTION;DECLAREis_used_true EXCEPTION; --声明异常名称 is_used_false exception;v_bool boolean := true;BEGINif v_bool thenraise is_used_true;elseraise is_used_false;end if;EXCEPTIONWHEN is_used_true THEN --捕获异常DBMS_OUTPUT.put_line('捕获异常is_used_true');when is_used_false thenDBMS_OUTPUT.put_line('捕获异常is_used_false');END; /
--异常名称和错误代码相关联
--oracle只给一部分异常定义了名字,还有上千个其它错误只有错误数字和消息
--也可以使用使用RAISE_APPlication_ERROR抛出只有错误数字(-20999,-20000)和错误信息的异常,比如
DECLAREv_bool boolean := true;BEGINif v_bool thenraise_application_error(-20000, 'test true');elseraise_application_error(-20001, 'test FALSE');end if;EXCEPTIONWHEN OTHERS THEN--捕获异常 DBMS_OUTPUT.put_line(SQLERRM);IF SQLCODE = -20000 THEN --SQLCODE是一个内置函数,返回最后抛出错误的数值DBMS_OUTPUT.put_line('IT IS TRUE ERROR');ELSIF SQLCODE = -20001 THENDBMS_OUTPUT.put_line('IT IS FALSE ERROR');END IF; END; / --EXCEPTION_INIT可以将用EXCEPTION定义的异常名称和一个指定的错误数字关联到一起,一旦创建了关联关系 --可以通过名字抛出异常 declareis_used_true EXCEPTION;is_used_false EXCEPTION;pragma exception_init(is_used_true, -20000);pragma exception_init(is_used_false, -20001);--关联的数字不能是-1403--不可以使用0或者100之外的任何正数--不可以是小于-1000000的负数v_bool boolean := false;beginif v_bool thenraise_application_error(-20000, 'test true');elseraise_application_error(-20001, 'test FALSE');end if; EXCEPTIONWHEN is_used_true THENDBMS_OUTPUT.put_line('IT IS TRUE ERROR');when is_used_false thenDBMS_OUTPUT.put_line(sqlcode);DBMS_OUTPUT.put_line('it is false error'); end; /
--在两种场合下,建议使用EXCEPTION_INIT
--为一些经常用到的,匿名的系统异常命名
--为使用RAISE_APPLICATION_ERROR抛出的应用专有错误命名
declarev_bool boolean := false;beginif v_bool thenraise_application_error(myerrortype.n_true_error, 'test true');elseraise_application_error(myerrortype.n_false_error, 'test FALSE');end if; EXCEPTIONWHEN myerrortype.is_used_true THENDBMS_OUTPUT.put_line('IT IS TRUE ERROR');when myerrortype.is_used_false thenDBMS_OUTPUT.put_line('it is false error'); end; / create or replace package myerrortype is is_used_true EXCEPTION;is_used_false EXCEPTION;n_true_error number := -20000;n_false_error number := -20001;pragma exception_init(is_used_true, -20000);pragma exception_init(is_used_false, -20001);end myerrortype; /
---被命名的系统异常
--内置包standard,最常用的命名异常可以在这个包中找到,缺省包,使用其中异常不需要加上包名,比如
when standard.no_data_found then
when not_data_found then
--也有其它预定义的异常在其它内置包中,比如DBMS_LOB包,不是缺省包,使用时需要加上包名
DBMS_LOB.opt_deduplicate
--常用的异常
--ZERO_DIVIDE ora-01476 除零错误
--VALUE_ERROR ORA-06502 plsql在做类型转换、数值截断、或者数值、字符数据的无效约束
--transaction_backed_out ora-00061一个事务的远程部分被回滚
--too_many_values ora-01422 select into 返回了多行记录
--not_data_found ora-01403 sqlcode = 100
--异常的作用范围
--被命名的系统异常:全局可用
--被命名的程序定义的异常:只能在程序块中使用,如果是包中定义的异常,有包的EXECUTE权限都可以使用
--匿名系统异常:在任何程序块中,使用WHEN OTHERS THEN 部分处理
--匿名的自定义异常:调用RAISE_APPLICATION_ERROR是定义,并调用
CREATE OR REPLACE PROCEDURE TEST_EXCEPTION IS IS_TEST_EXCEPTION EXCEPTION ; BEGIN RAISE IS_TEST_EXCEPTION ;END ; /declare IS_TEST_EXCEPTION EXCEPTION ; BEGINDECLARE IS_TEST_EXCEPTION EXCEPTION ;BEGIN RAISE IS_TEST_EXCEPTION;END ;EXCEPTIONWHEN IS_TEST_EXCEPTION THENDBMS_OUTPUT.put_line('自定义异常');END; /
--抛出异常 --数据库检测到错误时,可以抛出异常 --使用RAISE语句抛出异常 --使用内置的RAISE_APPLICATION_ERROR过程抛出异常--RAISE语句 raise exception_name;--可以抛出当前块自定义的异常,也可以是系统定义异常 raise pkg_name.exception_name; --包中声明的异常 raise; --不需要异常名称,只能异常处理单元 WHEN 语句中使用,传播异常,在异常单元再次抛出同一个异常DECLAREis_used_true EXCEPTION;v_bool boolean := false;BEGINBEGINIF not v_bool THENRAISE is_used_true;END IF;EXCEPTIONWHEN is_used_true THENDBMS_OUTPUT.put_line('再次抛出is_used_true');raise;END; exceptionwhen is_used_true thenDBMS_OUTPUT.put_line('捕获到内层抛出的异常is_used_true');END; /
--raise_application_error,相较于raise,可以给异常加上一段错误信息
--执行这个过程,当前PLSQL块的执行会被终止,对out或者in out参数(没有使用NOCOPY)所做的修改会被撤销
--但对于全局数据结构的修改,比如包变量,数据库对象,不会回滚,必须使用rollback
create or replace procedure test_error is beginupdate ma_users t set t.user_point = 0 where t.user_name = '乱世佳人';RAISE_APPLICATION_ERROR(-20008, '更新错误'); end; / begintest_error; exceptionwhen others then ROLLBACK; end; /
--处理异常
--一旦有异常抛出,当前PL/SQL块就会终止正常执行,把控制传递给异常处理单元,这个异常或者
--被当前PL/SQL块中的处理句柄处理或者抛给外层块
DECLARE BEGIN [EXCEPTION--EXCEPTION HANDLERS] END ; --异常句柄语法 WHEN EXCEPTION_NAME [OR EXCEPTION_NAME] THEN EXECUTABLE STATEMENTS --或者 WHEN OTHERS THEN EXECUTABLE STATEMENTS;--一个异常处理单元可以有多个异常句柄,在结构上类似条件CASE语句 exception when no_data_found then --doing something when ... then -- doing something when others then -- doing something end ;
--内置的错误函数
--SQLCODE:返回代码中最后一次抛出的错误代码。如果没有任何错误,则返回0;
--SQLERRM:返回某个错误代码对应的错误信息。如果没有给SQLERRM传递错误代码,就会返回SQLCODE的错误代码--累积信息
--对应的消息,最大长度512个字节
DECLAREv_num number(11, 10) := 2;BEGINdbms_output.put_line('sqlcode1:=' || sqlcode);dbms_output.put_line('sqlerrm:=' || sqlerrm);dbms_output.put_line('sqlerrm1:=' || sqlerrm(-1476));beginv_num := 35;exceptionwhen others thendbms_output.put_line('sqlcode2:=' || sqlcode);dbms_output.put_line('sqlerrm2:=' || sqlerrm);v_num := v_num / 0;end;exceptionwhen others thendbms_output.put_line('sqlcode3:=' || sqlcode);dbms_output.put_line('sqlerrm3:=' || sqlerrm); END; / -- 其它函数 DECLAREv_num number(11, 10) := 2;BEGINbeginv_num := 35;exceptionwhen others thendbms_output.put_line('errorline:=' ||dbms_utility.format_error_backtrace);--dbms_output.put_line('errorline:=' ||dbms_utility.format_call_stack);v_num := v_num / 0;end;exceptionwhen others thendbms_output.put_line('errorline:=' ||dbms_utility.format_error_backtrace);END; /
--能够定位到异常的行数,想一想就激动人心
--在一个单独的句柄中包含多个异常
--未处理的异常会怎样?
--在外层块或者个程序
--捕获任何可能传播过来的异常
--为错误记录日志,从而开发人员能够分析是什么造成了这个问题
--返回一个状态码,描述或其他信息,以帮助宿主环境决定采取适当的措施
--异常的传播 DECLAREv_num number(11, 10) := 2;BEGINbeginv_num := 35;exceptionwhen no_data_found thendbms_output.put_line('no data found '||sqlerrm);WHEN OTHERS THEN dbms_output.put_line('TEST2'||sqlerrm);end;dbms_output.put_line('DOING SOMETHING'); exceptionwhen others thendbms_output.put_line(sqlerrm);END; / -- when others 的使用--构建一个有效的错误管理架构 --确定异常管理策略 --是否要在每一个PL/SQL中都包含一个异常处理单元 --是否应该只在最外层或者最顶层块中包含一个异常处理单元 --当错误发生时,如何管理事务? --对不同类的异常标准话处理:deliberate,unfortunate,unexpected --组织好对应用专有错误代码的使用,使用配置表配置? --使用标准化的错误管理程序 --创建通用错误处理的标准模板
----------------------------------------------------------------------------------------------------------------------
1. 最近招人做了些面试,凡是简历上写熟练掌握PL/SQL,我都会问在PL/SQL中怎么捕获异常,怎么抛出异常,
怎么获取异常信息或发生异常的行号?这些问题作为第一道作业。大家学完异常处理这节课后,用自己的语言描述下即可。
1:对于常见的oracle预定于的异常,no_data_found和too_many_rows是经常发生的异常,对于plsql要捕获
2:对于预定于的异常,当发生异常的时候,会自动根据名字与oracle内部定义的sqlcode(异常编号)和 sqlerrm(异常消息)关联。
3:如果将有异常代码区的块不捕获异常,则自动传播到外层块。
4:用户自定义的异常,需要声明,显式通过raise,raise_application_error抛出
5:WHEN OTHERS总是放在最后
2. 构建一个好的系统,最好有一个通用的记录异常方法以简化代码。还是之前的订购网站,
请大家设计一个异常日志表,可以记录异常发生时的时间,操作人,方法名,发生异常时的行号,
异常信息,错误信息级别(比如提示信息,错误,重要等)。并且设计一个通用的方法供发生异常时调用。
--异常日志表create table exception_logs (created_on date default sysdate, created_by varchar2(40) default 'system', option_users varchar2(32), method_name varchar2(32), exception_time date, exception_line varchar2(32), exception_code number, exception_message varchar2(500), exception_level varchar2(32));create table exception_level ( created_on date default sysdate, created_by varchar2(40) default 'system', exception_level number, exception_type varchar2(20), exception_code number);--exception_level --dbms_utility.format_error_backtrace/SQLERRM/sqlcode --0 未定义 --1 提示信息(数据不存在 no_data_found ora-01403/ORA-01403/100) --2 错误信息 (返回多行数据 too_many_rows ora-01422/ORA-01422,除数为0 ora-01476/ORA-01476 ,数字或值错误 ora-06502/ORA-06502,违反唯一约束条件 ora-65512/ORA-00001) --3 重要信息 ora-0600 create index idx_exception_level on exception_level (exception_code)insert into exception_level (exception_level,exception_type,exception_code) values (1,'deliberate',100); insert into exception_level (exception_level,exception_type,exception_code) values (2,'unfortunate',-1422); insert into exception_level (exception_level,exception_type,exception_code) values (2,'unfortunate',-1476); insert into exception_level (exception_level,exception_type,exception_code) values (2,'unfortunate',-6502); insert into exception_level (exception_level,exception_type,exception_code) values (2,'unfortunate',-1); insert into exception_level (exception_level,exception_type,exception_code) values (3,'unexpected',-600); commit; --package exceptionCREATE OR REPLACE PACKAGE SCOTT.exception_logs_pkg ISPROCEDURE exception_logs_p (i_option_users IN exception_logs.option_users%TYPE,i_method_name IN exception_logs.method_name%TYPE,i_exception_line IN exception_logs.exception_line%TYPE,i_exception_code IN exception_logs.exception_code%TYPE,i_exception_message IN exception_logs.exception_message%TYPE--i_exception_level IN exception_logs.exception_level%TYPE ); END exception_logs_pkg; /CREATE OR REPLACE PACKAGE BODY SCOTT.exception_logs_pkg IS/******************************************************************************NAME: exception_logs_pkgPURPOSE:REVISIONS:Ver Date Author Description--------- ---------- --------------- ------------------------------------1.0 2016-03-08 hongquan 1. Created this package body.******************************************************************************/PROCEDURE exception_logs_p (i_option_users IN exception_logs.option_users%TYPE,i_method_name IN exception_logs.method_name%TYPE,i_exception_line IN exception_logs.exception_line%TYPE,i_exception_code IN exception_logs.exception_code%TYPE,i_exception_message IN exception_logs.exception_message%TYPE--i_exception_level IN exception_logs.exception_level%TYPE )ISPRAGMA AUTONOMOUS_TRANSACTION;v_sysdate DATE DEFAULT SYSDATE;v_exception_level NUMBER DEFAULT 0;BEGIN BEGINSELECT exception_levelINTO v_exception_levelFROM exception_levelWHERE exception_code=i_exception_code;EXCEPTION WHEN OTHERS THENv_exception_level:=3;END ;BEGININSERT INTO exception_logs (option_users,method_name,exception_time,exception_line,exception_code,exception_message,exception_level)VALUES (i_option_users,i_method_name,v_sysdate,i_exception_line,i_exception_code,i_exception_message,v_exception_level);COMMIT;EXCEPTIONWHEN OTHERSTHENROLLBACK;END;END; END exception_logs_pkg; / --调用 DECLAREv_num number(11, 10) := 2;v_erroeline varchar2(100);--not a numberv_sqlcode number;v_sqlerrm varchar2(100);v_sysdate date default sysdate;v_user varchar2(32) default user;BEGINbegin--v_num := 35;--v_sysdate := 35;--v_num:=1/0;select ename into v_num from emp where empno=7521;--insert into emp(empno) values (7499); exceptionwhen others thenv_erroeline:=dbms_utility.format_error_backtrace;v_sqlcode:=sqlcode;v_sqlerrm:=substr(SQLERRM,1,100);dbms_output.put_line('v_erroeline=='||v_erroeline);dbms_output.put_line('v_sqlcode1=='||v_sqlcode);dbms_output.put_line('v_sqlerrm1=='||v_sqlerrm);dbms_output.put_line('errorline1:=' ||dbms_utility.format_error_backtrace);-- dbms_output.put_line('errorline2:=' ||dbms_utility.format_call_stack);exception_logs_pkg.exception_logs_p (v_user,'testerror',v_erroeline,v_sqlcode, v_sqlerrm); RAISE; end;exceptionwhen others thendbms_output.put_line('errorline3:=' ||dbms_utility.format_error_backtrace);--v_sqlcode:=sqlcode;--v_sqlerrm:=substr(SQLERRM,1,100);dbms_output.put_line('v_sqlcode2=='||v_sqlcode);dbms_output.put_line('v_sqlerrm2=='||v_sqlerrm);rollback; END; /