资源描述
单击此处编辑母版标题样式,第一级,第二级,第三级,第四级,*,home back first,prev,next last,PL/SQL,总结,-3,目录,处理异常,捕获,Oracle Server,异常,捕获用户定义异常,识别变量作用域,处理异常,本部分将讲解:,在,PL/SQL,代码中包含异常处理代码的优点,PL/SQL,块异常处理部分的目的,异常处理指南,处理异常,前面学了如何在,PL/SQL,块中编写声明部分和执行部分,.,所有要执行的,SQL,和,PL/SQL,代码都写在执行部分,.,迄今为止我们假设代码只有编译错误,.,然而,代码会发生难以预料的运行时错误,.,本部分学习如何在,PL/SQL,块中处理这些,运行时错误,.,什么是异常,?,异常是程序的运行时错误,异常会中断程序的正常执行,.,异常的产生有多种原因,:,用户输入错误,;,硬件错误,;,网页不存在,;,等等,.,你使用应用程序和网站时遇到过错误吗?,PL/SQL,中的异常,下例运行正常,.,但是如果输入的是,Korea,South,而不是,Republic of Korea,呢,?,DECLARE,v_country_name,wf_countries.country_name%,TYPE,:=,Republic of Korea,;v_elevation,wf_countries.highest_elevation%,TYPE,;,BEGIN,SELECT,highest_elevation,INTO,v_elevation,FROM,wf_countries,WHERE,country_name=v_country_name;DBMS_OUTPUT.PUT_LINE(v_country_name);,END,;,PL/SQL,中的异常,DECLARE,v_country_name,wf_countries.country_name%,TYPE,:=,Republic of Korea,;v_elevation,wf_countries.highest_elevation%,TYPE,;,BEGIN,SELECT,highest_elevation,INTO,v_elevation,FROM,wf_countries,WHERE,country_name=v_country_name;DBMS_OUTPUT.PUT_LINE(v_country_name);,END,;,PL/SQL,中的异常,代码没有按预期的运行,.,没有找到有关,Korea,South,的数据,因为国家名实际上是按,Republic of Korea,存储的,.,这类,PL/SQL,错误称为异常,.,异常发生时,我们说异常被,“,抛出”,.,异常被抛出时,异常点之后,PL/SQL,块执行部分的剩余代码不再执行,.,什么是异常处理代码,?,异常处理代码定义了异常发生后应当执行的恢复操作,.,编写代码时,程序员应当预见到代码执行时可能发生的错误的类型,.,然后为每一种错误编写异常处理代码,.,异常处理代码是程序员为代码错误编制的处理预案,.,什么是异常处理代码,?,程序员使用异常处理代码来处理哪些错误,?,系统错误,(,比如磁盘空间耗尽,),数据错误,(,比如,主键值重复,),用户错误,(,比如,数据输入错误,),很多其它的可能性,!,为什么异常处理重要,?,你能说明为什么异常处理如此重要吗,?,可能的原因包括,:,减轻错误对用户的 影响,(,频繁的错误使用户沮丧甚至拒绝使用程序,),保护数据库,(,避免数据丢失或被覆盖,),错误消耗大量系统资源,(,错误发生后,纠正错误代价高昂,;,用户频繁打电话请求帮助,).,代码更加易读,因为错误处理代码可在同一个块中的独立部分处理错误,.,处理,PL/SQL,异常,PL/SQL,抛出异常,块执行结束,但可编写异常处理代码,在块结束前执行最后的操作,.,异常处理部分以关键字,EXCEPTION,开始,.,处理,PL/SQL,异常,异常如果被处理,PL/SQL,程序就不会突然中断,.,异常抛出后,控制转到异常处理部分,执行相应的处理代码,.,之后,,PL/SQL,块正常、成功结束,.,一个时刻只能发生一个异常,.,异常发生时,PL/SQL,在块结束前只执行一个异常处理代码,.,处理,PL/SQL,异常,点,A,的代码不会执行,因为,SELECT,语句失败,.,处理,PL/SQL,异常,下面是另一示例,.,块中,select,语句用于获取,John,的,last_name,.,DECLARE,v_lname,VARCHAR2,(,15,);,BEGIN,SELECT,last_name,INTO,v_lname,FROM,employees,WHERE,first_name,like,El%,;DBMS_OUTPUT.PUT_LINE(,Ellens last name is:,|,v_lname,);,END,;,然而,因为有多个,John,所以会产生异常,.,处理,PL/SQL,异常,下例中异常处理代码用于处理 预定义的,Oracle,服务错误,TOO_MANY_ROWS.,下部分将详细学习预定义的,Oracle,服务错误,.,DECLARE,v_lname,employees.last_name%,TYPE,;,BEGIN,SELECT,last_name,INTO,v_lname,FROM,employees,WHERE,first_name,like,El%,;DBMS_OUTPUT.PUT_LINE(,Ellens last name is:,|,v_lname,);,EXCEPTION,WHEN,TOO_MANY_ROWS,THEN,DBMS_OUTPUT.PUT_LINE(,Your select statement retrieved multiple rows.Consider using a cursor.,);,END,;,捕获异常,你可通过在,PL/SQL,块的异常处理部分增加相应的处理代码来处理或捕获(,trap,)任何错误,.,语法,:,捕获异常,每个异常处理代码包含一个,WHEN,子句,其后是异常名,然后是,THEN,引导的异常处理语句,.,在,EXCEPTION,部分可包含任意多个异常处理代码来处理特定的异常,.,然而,不能为同一个异常编写多个异常处理代码,.,捕获异常,语法中,:,exception,是预定义的异常名或用户在声明部分定义的异常名,statement,是一个或多个,PL/SQL,或,SQL,语句,OTHERS,是可选的异常处理子句,用于捕获 前面没有明确捕获的任何异常,OTHERS,关键字,异常处理部分只捕获那些,WHEN,子句说明的异常,;,其它任何异常都不会被捕获,除非后面也使用了,OTHERS,子句,.,OTHERS,子句捕获前面没有捕获的异常,.,如果使用,OTHERS,必须是异常处理最后一个子句,.,OTHERS,关键字,考虑下面的例子,:,如果程序抛出,NO_DATA_FOUND,异常,则执行,statement1,如果程序抛出,TOO_MANY_ROWS,异常,则执行,statement2,如果程序抛出了其他异常,则执行,statement3,捕获异常指南,如果有可能发生错误,就要添加异常处理代码,.,在计算时,字符串操作时,执行,SQL,语句时都有可能发生错误,.,如果有可能,尽量按名称处理异常 而不是用,OTHERS,捕获异常,.,掌握预定义异常的名称及其产生的原因,.,用不同的坏数据及其组合测试代码,发现可能出现的错误,.,在异常处理代码中输出调试信息,.,仔细考虑每个异常处理代码是需要提交事务,还是撤销事务,或是让事务继续,.,不管错误多么严重,我们都要使数据库处于一致状态,避免保存任何坏数据,.,处理异常,本部分讲解了:,在,PL/SQL,代码中包含异常处理代码的优点,PL/SQL,块异常处理部分的目的,异常处理指南,捕获,Oracle Server,异常,本部分将讲解:,Oracle,服务异常,用户自定义异常,显式和隐式抛出的异常,捕获预定义的,Oracle Server,异常,捕获非预定义的,Oracle Server,错误,通过错误代码和错误消息识别异常,捕获,Oracle Server,异常,PL/SQL,错误处理非常灵活,允许程序员处理用户定义异常和,Oracle,定义异常,.,本部分学习预定义和非预定义,Oracle,服务错误,.,预定义错误是常见的,Oracle,错误,为了方便,,PL/SQL,已经为其定义了异常名,.,非预定义错误使用,ORA,错误代码和消息,.,两种错误处理语法不同,但你可在,EXCEPTION,处理部分捕获所有这两种错误,.,Exception Types,在,PL/SQL,中处理异常,有两种方式抛出异常:,Oracle,服务隐式(自动)抛出,:,Oracle,服务发生错误时,异常自动抛出,.,例如,单行,SELECT,查询如果没有返回数据,则发生编号,ORA-01403,的错误,then PL/SQL,抛出,NO_DATA_FOUND,异常,.,程序员显式(明确)抛出,:,根据程序要实现的功能,有时需要显式抛出异常,.,可通过,RAISE,语句明确的抛出异常,.,RAISE,语句抛出的异常可以是用户定义的,也可是,Oracle,预先定义好的,.,下一部分详细解释,.,两种,Oracle,服务错误,Oracle,服务发生错误时,相关异常自动抛出,执行部分剩余代码被忽略,在异常处理部分寻找对应异常处理代码,.,有两种,Oracle,服务错误,:,预定义,Oracle,服务错误,:,此类错误有预先定义好的异常名,.,如,错误,ORA-01403,的异常名为,NO_DATA_FOUND,.,非预定义,Oracle,服务错误,:,此类错误没有预先定义好的异常名,只有形如,(ORA-,nnnnn,),的标准错误编号和错误描述消息,.,可以为此类错误声明一个异常名,从而可以在异常处理部分使用此名称捕获该异常,.,捕获预定义,Oracle,服务错误,在异常处理代码中引用预定义的异常名,.,下面是几个预定义异常的例子,:,NO_DATA_FOUND,TOO_MANY_ROWS,INVALID_CURSOR,ZERO_DIVIDE,DUP_VAL_ON_INDEX,plsql_s06_l02_predefined_errors.doc,文档中给出了部分预定义异常,.,完整异常列表参看,PL/SQL Users Guide and Reference,.,捕获预定义,Oracle,服务错误,下例中使用了预定义,Oracle,错误,TOO_MANY_ROWS,注意预定义,Oracle,错误无需声明,直接使用,DECLARE,v_lname,VARCHAR2,(,15,);,BEGIN,SELECT,last_name,INTO,v_lname,FROM,employees,WHERE,first_name,like,El%,;DBMS_OUTPUT.PUT_LINE(,Ellens last name is:,|,v_lname,);,EXCEPTION,WHEN,TOO_MANY_ROWS,THEN,DBMS_OUTPUT.PUT_LINE(,Your select statement retrieved multiple rows.Consider using a cursor.,);,END,;,捕获多个预定义,Oracle,服务错误,下例处理了,TOO_MANY_ROWS,和,NO_DATA_FOUND,异常,并使用,OTHERS,来处理其它可能的异常,.,DECLARE,v_lname,VARCHAR2,(,15,);,BEGIN,SELECT,last_name,INTO,v_lname,FROM,employees,WHERE,first_name=,John,;DBMS_OUTPUT.PUT_LINE(,Johns last name is:,|,v_lname,);,EXCEPTION,WHEN,TOO_MANY_ROWS,THEN,DBMS_OUTPUT.PUT_LINE(,Select statement found multiple rows,);,WHEN,NO_DATA_FOUND,THEN,DBMS_OUTPUT.PUT_LINE(,Select statement found no rows,);,WHEN,OTHERS,THEN,DBMS_OUTPUT.PUT_LINE(,Another type of error occurred,);,END,;,捕获非预定义,Oracle,服务错误,非预定义,Oracle,异常和预定义异常类似,;,但在,PL/SQL,中没有预定义的名称,.,它们是标准,Oracle,服务错误,有,ORA-,错误编号,.,在,DECLARE,部分程序员自己为错误定义一个异常名,并使用,PRAGMA EXCEPTION_INIT,函数将其与,ORA-,错误编号关联在一起,.,捕获非预定义,Oracle,服务错误,要捕获非预定义,Oracle,服务错误,必须先声明异常,.,声明的异常是,自动抛出的,.,在,PL/SQL,中,PRAGMA EXCEPTION_INIT,指示编译器将异常名和,Oracle,错误编号关联,.,这样,就可以通过异常名来捕获非预定义,Oracle,服务错误,并为其提供专门的异常处理代码,.,非预定义,Oracle,服务错误,示例,BEGIN,INSERT,INTO,departments (department_id,department_name),VALUES,(,280,NULL,);,END,;,INSERT,语句试图为,departments,表,的,department_name,列,插入空值,.,然而语句失败,因为,department_name,定义为,NOT NULL,.,违反,NOT NULL,约束这种错误没有预定义好的异常名称,.,处理这种错误的方法是自己声明异常名并将其与,ORA-1400,错误编号关联在一起,.,非预定义,Oracle,服务错误,1.,在声明部分定义一个异常名,.,非预定义,Oracle,服务错误,2.,使用,PRAGMA EXCEPTION_INIT,函数将异常名和标准,Oracle,错误编号关联,.,非预定义,Oracle,服务错误,3.,在异常处理代码中引用前面定义的异常名,异常相关函数,异常发生时,可通过下面两个函数获取相关的错误代码和错误消息,.,基于错误代码和错误消息可以决定后续操作,.,SQLERRM,返回文本类型的错误消息,SQLCODE,返回数值类型的错误代码,(,可赋予,NUMBER,类型变量,.),异常相关函数,不能直接在,SQL,语句中使用,SQLCODE,或,SQLERRM,.,必须先将其值赋予局部变量,然后在,SQL,语句中使用变量,如下例所示,:,捕获,Oracle Server,异常,本部分讲解了:,Oracle,服务异常,用户自定义异常,显式和隐式抛出的异常,捕获预定义的,Oracle Server,异常,捕获非预定义的,Oracle Server,错误,通过错误代码和错误消息识别异常,捕获用户定义异常,此部分将讲解,:,编写,PL/SQL,代码定义用户定义异常,编写,PL/SQL,代码抛出异常,处理抛出的异常,使用,RAISE_APPLICATION_ERROR,捕获用户定义异常,PL/SQL,处理的另一类错误是用户定义异常,.,此类错误不是由,Oracle,服务自动抛出,而是程序员在自己的代码中定义并抛出的,.,自定义错误的一个例子是非法经理,ID,:,INVALID_MANAGER_ID.,也可以为自定义错误定义错误编号和错误消息,.,异常类型,本部分学习用户定义异常,.,捕获,用户定义异常,PL/SQL,允许自定义异常,.,根据应用程序需要定义自己的异常,.,捕获,用户定义异常,需要自定义异常的一个例子是,你需要处理和强调输入数据的错误,.,例如,假设 程序需要提示用户输入部门编号和姓名,用于修改部门数据,.,DECLARE,v_name,VARCHAR2,(,20,):=,Accounting,;,v_deptno,NUMBER,:=,27,;,BEGIN,UPDATE,departments,SET,department_name=v_name,WHERE,department_id=,v_deptno,;,END,;,如果用户输入不存在的部门编号会怎样,?,上面代码不会出现,Oracle,服务错误,.,你需要自定义异常来提示数据输入的错误,.,捕获,用户定义异常,自定义异常的方法是,:,1.,在声明部分定义异常的名称,.,2.,使用,RAISE,语句在执行部分显式抛出异常,.,3.,在异常处理部分通过自定义异常名捕获异常,.,捕获,用户定义异常,下面是完整的代码,.,捕获,用户定义异常,1.,在声明部分定义异常的名称,.,语法,:,exception,EXCEPTION;,其中,:,exception,是异常的名称,捕获,用户定义异常,2.,使用,RAISE,语句在执行部分显式抛出异常,.,语法,:RAISE,exception,;,其中,:,exception,是前面定义的异常名,捕获,用户定义异常,3.,在异常处理部分通过自定义异常名捕获异常,.,RAISE,语句,RAISE,语句用于抛出已命名的异常,.,可以抛出,:,自定义异常,Oracle,服务异常,RAISE_APPLICATION_ERROR,可用,RAISE_APPLICATION_ERROR,过程从存储程序中返回给用户自定义的错误消息,.,使用,RAISE_APPLICATION_ERROR,的主要优点是,和,RAISE,相比,,RAISE_APPLICATION_ERROR,允许为自定义的异常指定错误编号和错误消息,.,错误编号必须在,-20000,和,-20999,之间,.,语法,:,RAISE_APPLICATION_ERROR,error_number,是自定义错误编号,自定义的异常错误编号必须在,20000,和,20999,之间,Message,是为错误自定义的错误消息,.,是可长达,2,048,字节的字符串,.,TRUE,|,FALSE,是可选的,Boolean,参数,(,TRUE,此错误在前面出现的所有错误之前显示,.,FALSE,此错误取代前面出现的所有错误,前面的其它错误不再有提示信息,.),范围,-20000,到,-20999,的错误编号预留给程序员使用,预定义的,Oracle,服务错误不会使用,.,RAISE_APPLICATION_ERROR,RAISE_APPLICATION_ERROR,可用于,:,执行部分,异常处理部分,执行部分的,RAISE_APPLICATION_ERROR,RAISE_APPLICATION_ERROR,过程为用户显示错误编号和消息,.,这和,Oracle,服务错误的处理方式是一致的,.,DECLARE,v_mgr,PLS_INTEGER,:=,123,;,BEGIN,DELETE,FROM,employees,WHERE,manager_id=v_mgr;,IF,SQL,%,NOTFOUND,THEN,RAISE_APPLICATION_ERROR(-,20202,This is not a valid manager,);,END,IF,;,END,;,异常部分的,RAISE_APPLICATION_ERROR,DECLARE,v_mgr,PLS_INTEGER,:=,27,;v_employee_id employees.employee_id%,TYPE,;,BEGIN,SELECT,employee_id,into,v_employee_id,FROM,employees,WHERE,manager_id=v_mgr;DBMS_OUTPUT.PUT_LINE(,The employee who works for manager_id,|v_mgr|,is:,|v_employee_id);,EXCEPTION,WHEN,NO_DATA_FOUND,THEN,RAISE_APPLICATION_ERROR(-,20201,This manager has no employees,);,WHEN,TOO_MANY_ROWS,THEN,RAISE_APPLICATION_ERROR(-,20202,Too many employees were found.,);,END,;,保留前面的错误,DECLARE,v_mgr,PLS_INTEGER,:=,27,;v_employee_id employees.employee_id%,TYPE,;,BEGIN,SELECT,employee_id,into,v_employee_id,FROM,employees,WHERE,manager_id=v_mgr;,EXCEPTION,WHEN,NO_DATA_FOUND,THEN,RAISE_APPLICATION_ERROR(-,20201,This manager has no employees,true,);,END,;,RAISE_APPLICATION_ERROR,与自定义异常合用,捕获用户定义异常,此部分讲解了,:,编写,PL/SQL,代码定义用户定义异常,编写,PL/SQL,代码抛出异常,处理抛出的异常,使用,RAISE_APPLICATION_ERROR,识别变量作用域,此部分将讲解:,描述在嵌套块中变量的作用域,.,使用标签限定嵌套块中的变量,描述异常的作用域,识别嵌套块中异常的作用域问题,描述异常在嵌套块中的传播方式及其影响,识别变量作用域,前面已学习过嵌套块,变量的作用域和异常的传播,.,理解了如何正确处理异常,本部分先复习前面知识,然后再讲述新的内容,.,命名异常是一种,PL/SQL,变量,.,要正确处理异常,需要理解异常变量的作用域和可见性,.,这在使用嵌套块时尤其重要,.,复习嵌套块,下例有外层,(,父,),块,(,蓝色显示,),和 嵌套,(,子,),块,(,红色显示,).,变量,v_outer_variable,在父块中定义,变量,v_inner_variable,在子块中定义,.,复习变量作用域,变量作用域是变量在其中可被访问和使用的一个块或多个块,.,PL/SQL,中,变量作用域是其定义所在块及其所有子块,.,下例两个变量的作用域是什么,?,复习变量作用域,阅读下面代码,.,每个变量的作用域是什么,?,复习变量作用域,下面代码为什么出错,?,复习变量作用域,下面代码正确吗,?,为什么,?,PL/SQL,怎样寻找变量,?,在块中使用变量时,PL/SQL,先在当前块中寻找该变量,(,局部变量,).,如果未找到,PL/SQL,继续在父块中寻找,.,如果还未找到,PL/SQL,在父块的父块中寻找,(,嵌套可有三层或更多层,).,依此类推,.,下例中有三层嵌套,.,三层嵌套示例,每个变量的作用域是什么,?,复习变量命名,下面变量声明对不对,?,以上声明合法,但在子块中无法访问父块定义的变量,v_myvar,.,复习变量命名,变量,v_date_of_birth,声明了两次,.,DBMS_OUTPUT.PUT_LINE,语句中引用的是哪个,v_date_of_birth,?,复习变量可见范围,变量可见范围是变量作用域中,不加限定符就可使用变量的部分,.,下面每个变量的可见范围是什么,?,复习变量可见范围,父块的,v_date_of_birth,变量作用域包括子块,.,此变量在父块中可见,.,然而,该变量在子块中不可见(被隐藏),因为子块中有一个同名的局部变量,.,变量,v_father_name,在父子块中都可见,.,变量,v_child_name,只在子块中可见,.,怎样才能在子块中引用父块定义的变量,v_date_of_birth,呢,?,复习块标签,可使用,语法为块加标签,.,然后通过标签使用在作用域内但被隐藏的变量,.,下例中,父块标签为,outer.,不仅父块,任何块都可加标签,.,通过块标签使用被隐藏变量,通过使用标签,outer,限定变量,v_date_of_birth,在子块中就可输出显示父亲的生日了,.,嵌套块中的异常处理,异常处理方式有两种,:,在发生异常的块处理,(,捕获,),该异常,将异常传递给调用环境,(,比如其父块,),传播异常到父块,如果异常自子块的执行部分抛出,子块无相应处理代码,PL/SQL,子块以失败结束,异常传递给其父块,.,传播异常到父块,下例中,子块执行时产生了一个异常,.,子块,EXCEPTION,部分 没有处理此异常,.,子块失败结束,,PL/SQL,将异常传递给父块,.,父块,EXCEPTION,部分成功处理此异常,.,从子块向外传递异常,如果,PL/SQL,产生异常,而当前块没有捕获该异常,异常连续传递给上层块,直到找到相应异常处理程序,.,异常传递给上层块时,当前块剩余执行代码被跳过,不再执行,.,这样做的优点是在块中处理此块可以处理的异常,将更一般的异常处理,(,如,WHEN OTHERS,),交给上层块去,.,下面是一个例子,.,从子块向外传递预定义异常,不存在编号,999,的员工,.,下面代码执行结果是什么,?,从子块向外传递用户定义的异常,下面代码执行结果怎样,?,异常名的作用域,预定义,Oracle,服务异常如,NO_DATA_FOUND,TOO_MANY_ROWS,(,以及,OTHERS,),不需要用户声明,.,可在任何块中使用(抛出或捕获),.,用户命名的异常,(,非预定义,Oracle,服务异常和 自定义异常,),是程序员定义的,EXCEPTION,类型的变量,其作用域和其它变量遵循相同规则,.,因此,子块声明的自定义异常不能在父块中使用,识别变量作用域,此部分讲解了:,描述在嵌套块中变量的作用域,.,使用标签限定嵌套块中的变量,描述异常的作用域,识别嵌套块中异常的作用域问题,描述异常在嵌套块中的传播方式及其影响,
展开阅读全文