一、存储过程中异常的处理

原则如下:
1、存储过程中的异常要在本存储过程中进行处理。
2、存储过程中调用存储过程,被调用的如果发生了异常,一般父存储过程需要终止。
3、子存储过程中的异常处理不得污染父存储过程中的异常处理。

在这样的原则下,每个存储过程都要进行异常的声明与处理,处理完毕之后要抛出异常对父存储过程进行中断,否则因为子存储过程中的异常已经得到了处理,父存储过程会继续执行下去。又因为不能使父存储过程的异常处理被污染,所以最好的处理异常的方式就是声明变量,该变量专门为异常而存在,发生异常则变量值改变,处理完异常后,在父存储过程中判断该变量的值,如果子存储过程中发生了异常,则主动抛出异常,中断父存储过程的运行,这样就能分清楚异常到底是发生在具体的哪个存储过程中了。
如上。
测试存储过程:

CREATE OR REPLACE PACKAGE TEST_ROBACK IS

  PROCEDURE PROMAIN(O_FLAG OUT VARCHAR2, O_RESULT OUT VARCHAR2);

  PROCEDURE PROCH1(O_FLAG OUT VARCHAR2, O_RESULT OUT VARCHAR2);

END TEST_ROBACK;
CREATE OR REPLACE PACKAGE BODY TEST_ROBACK IS

  PROCEDURE PROMAIN(O_FLAG OUT VARCHAR2, O_RESULT OUT VARCHAR2) IS
    V_FLAG   VARCHAR2(1) := '0';
    V_RESULT VARCHAR2(1000) := '';
    E_EXCEPTION EXCEPTION;
  BEGIN
    O_FLAG   := '0';
    O_RESULT := '处理成功';
    PROCH1(O_FLAG => V_FLAG, O_RESULT => V_RESULT);
    IF (V_FLAG != '0') THEN
      RAISE E_EXCEPTION;
    END IF;
    COMMIT;
  EXCEPTION
    WHEN E_EXCEPTION THEN
      O_FLAG   := '1';
      O_RESULT := '处理失败';
      DBMS_OUTPUT.PUT_LINE('子过程发生异常');
      ROLLBACK;
    WHEN OTHERS THEN
      O_FLAG   := '1';
      O_RESULT := '处理失败';
      DBMS_OUTPUT.PUT_LINE('主过程发生异常');
      ROLLBACK;
  END PROMAIN;

  PROCEDURE PROCH1(O_FLAG OUT VARCHAR2, O_RESULT OUT VARCHAR2) IS
    V_SQL VARCHAR2(1000) := '';
  
    --用来接收本存储过程中调用的其他的存储过程的返回结果
    V_FLAG   VARCHAR2(1) := '0';
    V_RESULT VARCHAR2(1000) := '';
    E_EXCEPTION EXCEPTION;
    --用来返回本存储过程的执行结果
  BEGIN
    O_FLAG   := '0';
    O_RESULT := '执行成功';
    V_SQL    := 'INSERT INTO AZWZ(CLAIMINFO) VALUES (''1'')';
    EXECUTE IMMEDIATE V_SQL;
    RAISE E_EXCEPTION;
  EXCEPTION
    WHEN E_EXCEPTION THEN
      O_FLAG   := '1';
      O_RESULT := '处理失败';
    WHEN OTHERS THEN
      O_FLAG   := '1';
      O_RESULT := '处理失败';
  END PROCH1;

BEGIN
  DBMS_OUTPUT.PUT_LINE('TEST');
END TEST_ROBACK;

关于raise_application_error的用法:

CREATE OR REPLACE PACKAGE BODY TEST_ROBACK IS

  PROCEDURE PROMAIN(O_FLAG OUT VARCHAR2, O_RESULT OUT VARCHAR2) IS
    E_EXCEPTION EXCEPTION;
    PRAGMA EXCEPTION_INIT(E_EXCEPTION, -20999);
  BEGIN
    O_FLAG   := '0';
    O_RESULT := '处理成功';
    PROCH1;
    COMMIT;
  EXCEPTION
    WHEN E_EXCEPTION THEN
      O_FLAG   := '1';
      O_RESULT := '处理失败';
      DBMS_OUTPUT.PUT_LINE(SQLERRM);
      ROLLBACK;
    WHEN OTHERS THEN
      O_FLAG   := '1';
      O_RESULT := '处理失败';
      DBMS_OUTPUT.PUT_LINE('主过程发生异常');
      ROLLBACK;
  END PROMAIN;

  PROCEDURE PROCH1 IS
    V_SQL VARCHAR2(1000) := '';
  BEGIN
    V_SQL := 'INSERT INTO AZWZ(CLAIMINFO,A) VALUES (''1'')';
    EXECUTE IMMEDIATE V_SQL;
  EXCEPTION
    WHEN OTHERS THEN
      RAISE_APPLICATION_ERROR(-20999, SQLERRM);
  END PROCH1;

BEGIN
  DBMS_OUTPUT.PUT_LINE('TEST');
END TEST_ROBACK;

综上,异常处理大概有两种方式:

  • 在子过程中进行异常处理,同时用改变返回值的方式在主过程中进行子过程是否发生异常的判断
  • 在主过程中定义异常号, 在子过程中进行异常处理,同时采用raise_application_error的方式抛出自定义异常号及异常信息,然后在主过程中直接捕获。这种方式的sqlcode只能在-20000到-20999

使用专门的表保存错误

将异常保存到专门的表格中:

-- Create table
create table A_LOG
(
  id             VARCHAR2(100) not null,
  proce_name     VARCHAR2(30),
  log_level      VARCHAR2(2),
  log_info       VARCHAR2(1000),
  makedate       DATE,
  maketime       VARCHAR2(10),
  standbystring1 VARCHAR2(100),
  standbystring2 VARCHAR2(100)
)
-- Create sequence 
create sequence MY_SEQ
minvalue 1
maxvalue 9999999999999999999999999999
start with 81
increment by 1
cache 20;

日志类存储过程:

create or replace package Z_LOG is

  INTO_LEVEL CONSTANT A_LOG.LOG_LEVEL%TYPE := '1';
  ERR_LEVEL  CONSTANT A_LOG.LOG_LEVEL%TYPE := '2';

  PROCEDURE LOG(I_PROCEDURENAME A_LOG.PROCE_NAME%TYPE,
                I_LOGLEVEL      A_LOG.LOG_LEVEL%TYPE,
                I_LOGINFO       A_LOG.LOG_INFO%TYPE);

end Z_LOG;
create or replace package body Z_LOG is

  PROCEDURE LOG(I_PROCEDURENAME A_LOG.PROCE_NAME%TYPE,
                I_LOGLEVEL      A_LOG.LOG_LEVEL%TYPE,
                I_LOGINFO       A_LOG.LOG_INFO%TYPE) IS
    PRAGMA AUTONOMOUS_TRANSACTION;
    V_SQL VARCHAR(1000);
    LOG_EXCEPTION EXCEPTION;
  BEGIN
    V_SQL := 'insert into A_LOG
  (ID, PROCE_NAME, LOG_LEVEL, LOG_INFO, MAKEDATE, MAKETIME)
VALUES
  (MY_SEQ.NEXTVAL,' || ':1,' || ':2,' || ':3,' ||
             'TRUNC(SYSDATE),TO_CHAR(SYSDATE,  ''HH24:MM:SS''))';
    EXECUTE IMMEDIATE V_SQL
      USING I_PROCEDURENAME, I_LOGLEVEL, I_LOGINFO;
    COMMIT;
  
  EXCEPTION
    WHEN OTHERS THEN
      RAISE LOG_EXCEPTION;
  END LOG;

end Z_LOG;

测试类:

CREATE OR REPLACE PACKAGE TEST_ROBACK IS

  PROCEDURE PROMAIN(O_FLAG OUT VARCHAR2, O_RESULT OUT VARCHAR2);

  PROCEDURE PROCH1;

END TEST_ROBACK;
CREATE OR REPLACE PACKAGE BODY TEST_ROBACK IS

  PROCEDURE PROMAIN(O_FLAG OUT VARCHAR2, O_RESULT OUT VARCHAR2) IS
    E_EXCEPTION EXCEPTION;
    PRAGMA EXCEPTION_INIT(E_EXCEPTION, -20009);
    V_PRO_NAME A_LOG.PROCE_NAME%TYPE := 'PROMAIN';
  BEGIN
    Z_LOG.LOG(V_PRO_NAME, '1', '开始调用主过程');
    O_FLAG   := '0';
    O_RESULT := '处理成功';
    PROCH1;
  EXCEPTION
    WHEN E_EXCEPTION THEN
      O_FLAG   := '1';
      O_RESULT := '处理失败';
      Z_LOG.LOG(V_PRO_NAME, '2', SQLERRM);
      ROLLBACK;
    WHEN OTHERS THEN
      O_FLAG   := '1';
      O_RESULT := '处理失败';
      Z_LOG.LOG(V_PRO_NAME, '2', SQLERRM);
      ROLLBACK;
  END PROMAIN;

  PROCEDURE PROCH1 IS
    V_SQL      VARCHAR2(1000) := '';
    V_PRO_NAME A_LOG.PROCE_NAME%TYPE := 'PROCH1';
    V_PARAM    VARCHAR2(2) := '1';
  BEGIN
    Z_LOG.LOG(V_PRO_NAME, '1', '开始调用子过程');
    insert into AZWZ (CLAIMINFO) VALUES ('2');
    V_SQL := 'INSERT INTO AZWZ(CLAIMINFO,A) VALUES (''' || V_PARAM || ''')';
    EXECUTE IMMEDIATE V_SQL;
  EXCEPTION
    WHEN OTHERS THEN
      Z_LOG.LOG(V_PRO_NAME, '2', V_SQL);
      ROLLBACK;
      RAISE_APPLICATION_ERROR(-20009, V_SQL);
  END PROCH1;

BEGIN
  DBMS_OUTPUT.PUT_LINE('TEST');
END TEST_ROBACK;

事务控制

在上面的示例中要注意事务控制,在日志类中采用自治事务。这样的话,每次commit就只提交本过程中的dml,而不会影响到调用本过程的其他过程的事务。

猜你喜欢

转载自blog.csdn.net/qq_37661639/article/details/89708702
今日推荐