zoukankan      html  css  js  c++  java
  • PL/SQL 训练03 --异常

    --程序员在开发的时候,经常天真的认为这个世界是完美的,用户如同自己般聪明,总能按照自己设想的方式
    --操作系统输入数据。但残酷的事实告诉我们,这是不可能的事情,用户总会跟我们相反的方式操作系统
    --于是,无数的问题砸向我们。
    --那么怎么办呢?
    ---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;
    
    DECLARE
      is_used_true EXCEPTION; --声明异常名称
      is_used_false exception;
      v_bool boolean := true;
    
    BEGIN
      if v_bool then
        raise is_used_true;
      else
        raise is_used_false;
      end if;
    
    EXCEPTION
      WHEN is_used_true THEN  --捕获异常
        DBMS_OUTPUT.put_line('捕获异常is_used_true');
      when is_used_false then
        DBMS_OUTPUT.put_line('捕获异常is_used_false');
      
    END;
    /

    --异常名称和错误代码相关联
    --oracle只给一部分异常定义了名字,还有上千个其它错误只有错误数字和消息
    --也可以使用使用RAISE_APPlication_ERROR抛出只有错误数字(-20999,-20000)和错误信息的异常,比如

    DECLARE
      v_bool boolean := true;
    
    BEGIN
      if v_bool then
        raise_application_error(-20000, 'test true');
      else
        raise_application_error(-20001, 'test FALSE');
      end if;
    
    EXCEPTION
      WHEN OTHERS THEN
        --捕获异常
        DBMS_OUTPUT.put_line(SQLERRM);
        IF SQLCODE = -20000 THEN  --SQLCODE是一个内置函数,返回最后抛出错误的数值
          DBMS_OUTPUT.put_line('IT IS TRUE ERROR');
        ELSIF SQLCODE = -20001 THEN
          DBMS_OUTPUT.put_line('IT IS FALSE ERROR');
        END IF;
    END;
    /
    --EXCEPTION_INIT可以将用EXCEPTION定义的异常名称和一个指定的错误数字关联到一起,一旦创建了关联关系
    --可以通过名字抛出异常
    declare
      is_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;
    
    begin
      if v_bool then
        raise_application_error(-20000, 'test true');
      else
        raise_application_error(-20001, 'test FALSE');
      end if;
    EXCEPTION
      WHEN is_used_true THEN
        DBMS_OUTPUT.put_line('IT IS TRUE ERROR');
      when is_used_false then
          DBMS_OUTPUT.put_line(sqlcode);
    
        DBMS_OUTPUT.put_line('it is false error');
    end;
    /

    --在两种场合下,建议使用EXCEPTION_INIT
    --为一些经常用到的,匿名的系统异常命名
    --为使用RAISE_APPLICATION_ERROR抛出的应用专有错误命名

    declare
      v_bool boolean := false;
    
    begin
      if v_bool then
        raise_application_error(myerrortype.n_true_error, 'test true');
      else
        raise_application_error(myerrortype.n_false_error, 'test FALSE');
      end if;
    EXCEPTION
      WHEN myerrortype.is_used_true THEN
        DBMS_OUTPUT.put_line('IT IS TRUE ERROR');
      when myerrortype.is_used_false then
        DBMS_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 ;
    BEGIN
       DECLARE 
        IS_TEST_EXCEPTION EXCEPTION ;
       BEGIN 
       
          RAISE IS_TEST_EXCEPTION;
       END ;
    
    EXCEPTION
      WHEN IS_TEST_EXCEPTION THEN
        DBMS_OUTPUT.put_line('自定义异常');
        
    END;
    /
    --抛出异常
    --数据库检测到错误时,可以抛出异常
    --使用RAISE语句抛出异常
    --使用内置的RAISE_APPLICATION_ERROR过程抛出异常
    
    --RAISE语句
    raise exception_name;--可以抛出当前块自定义的异常,也可以是系统定义异常
    raise pkg_name.exception_name; --包中声明的异常
    raise; --不需要异常名称,只能异常处理单元 WHEN 语句中使用,传播异常,在异常单元再次抛出同一个异常
    
    DECLARE
      is_used_true EXCEPTION;
      v_bool boolean := false;
    
    BEGIN
    
      BEGIN
        IF not v_bool THEN
          RAISE is_used_true;
        END IF;
      
      EXCEPTION
        WHEN is_used_true THEN
          DBMS_OUTPUT.put_line('再次抛出is_used_true');
          raise;
      END;
    exception
      when is_used_true then
        DBMS_OUTPUT.put_line('捕获到内层抛出的异常is_used_true');
      
    END;
    /

    --raise_application_error,相较于raise,可以给异常加上一段错误信息
    --执行这个过程,当前PLSQL块的执行会被终止,对out或者in out参数(没有使用NOCOPY)所做的修改会被撤销
    --但对于全局数据结构的修改,比如包变量,数据库对象,不会回滚,必须使用rollback

    create or replace procedure test_error is 
    
    begin
      update ma_users t set t.user_point = 0 where t.user_name = '乱世佳人';
    
      RAISE_APPLICATION_ERROR(-20008, '更新错误');
    end;
    /
    begin
      test_error;
    exception
       when 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个字节

    DECLARE
    
      v_num number(11, 10) := 2;
    
    BEGIN
    
      dbms_output.put_line('sqlcode1:=' || sqlcode);
      dbms_output.put_line('sqlerrm:=' || sqlerrm);
      dbms_output.put_line('sqlerrm1:=' || sqlerrm(-1476));
    
      begin
        v_num := 35;
      exception
        when others then
          dbms_output.put_line('sqlcode2:=' || sqlcode);
          dbms_output.put_line('sqlerrm2:=' || sqlerrm);
          v_num := v_num / 0;
      end;
    
    exception
      when others then
        dbms_output.put_line('sqlcode3:=' || sqlcode);
        dbms_output.put_line('sqlerrm3:=' || sqlerrm);
    END;
    /
    -- 其它函数
    DECLARE
    
      v_num number(11, 10) := 2;
    
    BEGIN
    
      begin
        v_num := 35;
      exception
        when others then
          dbms_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;
      
    exception
      when others then
        dbms_output.put_line('errorline:=' ||
                             dbms_utility.format_error_backtrace);
      
    END;
    /

    --能够定位到异常的行数,想一想就激动人心
    --在一个单独的句柄中包含多个异常
    --未处理的异常会怎样?

    --在外层块或者个程序
    --捕获任何可能传播过来的异常
    --为错误记录日志,从而开发人员能够分析是什么造成了这个问题
    --返回一个状态码,描述或其他信息,以帮助宿主环境决定采取适当的措施

    --异常的传播
    DECLARE
    
      v_num number(11, 10) := 2;
    
    BEGIN
    
      begin
        v_num := 35;
      exception
        when no_data_found then
           dbms_output.put_line('no data found '||sqlerrm);
        WHEN OTHERS THEN 
            dbms_output.put_line('TEST2'||sqlerrm);
      end;
      
         dbms_output.put_line('DOING SOMETHING');
    exception
      when others then
         dbms_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 exception
    
    CREATE OR REPLACE PACKAGE SCOTT.exception_logs_pkg
    IS
       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
          );
    END exception_logs_pkg;
    /
    
    
    CREATE OR REPLACE PACKAGE BODY SCOTT.exception_logs_pkg
    IS
       /******************************************************************************
          NAME:       exception_logs_pkg
          PURPOSE:
    
          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
          )
       IS
          PRAGMA AUTONOMOUS_TRANSACTION;
          v_sysdate           DATE DEFAULT SYSDATE;
          v_exception_level   NUMBER DEFAULT 0;
     BEGIN     
      BEGIN
       SELECT   exception_level
          INTO v_exception_level
         FROM exception_level
          WHERE exception_code=i_exception_code;
          
       EXCEPTION 
       WHEN OTHERS THEN
        v_exception_level:=3;
        END ;
        
       BEGIN
          INSERT 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;
       EXCEPTION
          WHEN OTHERS
          THEN
             ROLLBACK;
       END;
      END;
    END exception_logs_pkg;
    /
    --调用
    DECLARE
    
      v_num number(11, 10) := 2;
      v_erroeline varchar2(100);--not a number
      v_sqlcode number;
      v_sqlerrm varchar2(100);
      v_sysdate date default sysdate;
      v_user varchar2(32) default user;
    
    BEGIN
    
      begin
        --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);
      exception
        when others then
        v_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;
      
    exception
      when others then
        dbms_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;
    /
  • 相关阅读:
    segmentation fault(core dumped)
    (LIS LCS 例题)Max Sum Advanced Fruits Super Jumping! Jumping! Jumping!
    几种数学公式(环排列 母函数 唯一分解定理 卡特兰数 默慈金数 贝尔数 那罗延数)
    map set 详解
    算法录 之 二分和三分
    LIS 最长上升子序列 LCS 最长公共子序列 模板
    JAVA 大数据 例题
    Java 实现大数算法
    7 21 第一次团队赛——————写给队友
    离散化+unique()+二分查找
  • 原文地址:https://www.cnblogs.com/yhq1314/p/10613169.html
Copyright © 2011-2022 走看看