PLSQL存储过程中异常的传递
1.PLSQL异常处理的几个原则
-
异常是指程序在编译时不能被发现,而在运行时出现的错误,异常处理可以使程序正常结束 ,并且可以灵活指定异常处理动作。
-
异常的作用域是本异常所覆盖的语句块或部分语句块,对于嵌套的语句块,外部定义的异常适用于处理所有内部块中所匹配出现的异常;
-
异常出现后,在块中直接跳到异常处理部分,出现异常问题的后面非异常处理语句不会获得执行;
-
嵌套块中,内部块已处理过异常后,不会再传递进入外部块中处理,在内部块执行完成跳出内部块后,内部块外面外部块的语句仍按顺序执行,不会直接跳到外部块异常页结束块;
-
嵌套块中,内部块未进行异常处理时,会将异常往外层块传递,如果外层块中可以捕获到对应异常,则由外部块处理异常。
2. 栗子
2.1 单个存储过程
创建类似测试过程:
CREATE OR REPLACE PROCEDURE TEST_INNER_EXCEPTION
(O_CODE OUT NUMBER,
O_NOTE OUT VARCHAR2
)
IS
V_RESULT NUMBER;
BEGIN
O_CODE := 1;
O_NOTE :='SUCCESS';
SELECT 1/0 INTO V_RESULT FROM DUAL;
DBMS_OUTPUT.PUT_LINE('异常后是否执行');
EXCEPTION
WHEN OTHERS THEN
O_CODE :=-1;
O_NOTE :='Failure: '||SQLERRM;
END ;
数据库单步测试该过程,将会看到执行到1/0时会发生除零运行时异常,触发ORACLE内置OTHERS异常后,直接跳到EXCEPTION ,执行此异常后的语句,而不会执行发生异常后正常块语句:DMBS_OUTPUT.PUT_LINE打印语句:
2.2 嵌套存储过程
(1) 创建外层存储过程调用内层存储过程:内部处理异常,外部不再处理,执行外部异常只会得到外部结果,不会传递内部异常到外部。
CREATE OR REPLACE PROCEDURE TEST_OUTER_EXCEPTION
(O_CODE OUT NUMBER,
O_NOTE OUT VARCHAR2
)
IS
V_RESULT NUMBER;
BEGIN
TEST_INNER_EXCEPTION(O_CODE,O_NOTE);
O_CODE := 1;
O_NOTE :='SUCCESS';
EXCEPTION
WHEN OTHERS THEN
O_CODE :=-1;
O_NOTE :='Failure: '||SQLERRM;
END ;
单步调试此过程,将会看到在内部除零错误时,内部异常处理,此时O_CODE=-1,O_NOTE为内部失败信息,但注意此时,内部存储过程执行结束后,由于其异常已在其内部本身被处理,因而不会被传递到外部存储过程的异常处理部分,因此,跳出内部存储过程后,依然执行下一句:
O_CODE := 1;
O_NOTE :='SUCCESS';
因此,我们看到执行结果为成功,但实际上,此整体执行过程时,嵌套调用的内部存储过程发生了异常错误,而我们外部整体的存储过程执行结果,如果不主动处理的话,是看不出有什么报错的:
(2) 调整内部过程:不处理异常,异常传递到外部过程,整体调用外部过程,将抛出外部异常结果 :
CREATE OR REPLACE PROCEDURE TEST_INNER_EXCEPTION
(O_CODE OUT NUMBER,
O_NOTE OUT VARCHAR2
)
IS
V_RESULT NUMBER;
BEGIN
O_CODE := 1;
O_NOTE :='SUCCESS';
SELECT 1/0 INTO V_RESULT FROM DUAL;
DBMS_OUTPUT.PUT_LINE('异常后是否执行');
END ;
单独测试内部过程,无异常处理时程序会异常终止,无法正常结束:
整体测试外部过程,此时将会看到,由于异常在内部未处理,被传递到外部过程,直接到外部异常处理部分,而在外部过程中调用内部过程后的非异常语句:
O_CODE := 1;
O_NOTE :='SUCCESS';
不会被执行:
外部过程的整体执行返回结果,为外部异常处理时的结果:
(3)综合(1),(2), 当有存储过程的多层嵌套时,如果想要知道每个内部及整体过程的执行情况是否正常,要么就在每个内部过程中都做日志记录处理,要么就是在每个内部存储过程中不进行异常处理,让异常被最终被传递到最外层存储过程。但内部存储过程不进行异常处理也不够严谨,所以,一种方法是,内部主动记录处理结果日志,并且,在外部使用用户自定义异常,并在外部过程调用内部过程时,结合IF ELSE语句,通过内部过程的执行结果 ,将用户自定义异常传递出外部过程。多层嵌套时异常想要传递到最外层,则除了最里层被调用的过程,其余调用内部过程的外部过程,都需要抛出自定义异常:
内部过程输出结果,尽量做到信息的完整信,比如最少不能少了此错误发生的对象名:
CREATE OR REPLACE PROCEDURE TEST_INNER_EXCEPTION
(O_CODE OUT NUMBER,
O_NOTE OUT VARCHAR2
)
IS
V_RESULT NUMBER;
BEGIN
O_CODE := 1;
O_NOTE :='SUCCESS';
SELECT 1/0 INTO V_RESULT FROM DUAL;
DBMS_OUTPUT.PUT_LINE('异常后是否执行');
EXCEPTION
WHEN OTHERS THEN
O_CODE := -1;
O_NOTE :=$$PLSQL_UNIT||':Failure: '||SQLERRM;
END ;
外部过程使用自定义异常,最内部被调用过程不定义用户自定义异常,而只输出结果,由外部判断此结果,是否抛出自定义异常:
CREATE OR REPLACE PROCEDURE TEST_OUTER_EXCEPTION
(O_CODE OUT NUMBER,
O_NOTE OUT VARCHAR2
)
IS
TRANS_EXCEPTION EXCEPTION;
BEGIN
TEST_INNER_EXCEPTION(O_CODE,O_NOTE);
IF O_CODE =-1 THEN
RAISE TRANS_EXCEPTION;
END IF;
O_CODE := 1;
O_NOTE :='SUCCESS';
EXCEPTION
WHEN TRANS_EXCEPTION THEN
O_CODE :=-1;
O_NOTE :=$$PLSQL_UNIT||'->'||O_NOTE;
WHEN OTHERS THEN
O_CODE :=-1;
O_NOTE :=$$PLSQL_UNIT||':Failure: '||SQLERRM;
END ;
调用外部过程,将会看到,内部过程出现异常时,只输出结果,由外部过程判断,并抛出自定义异常,最终在外部过程的自定义异常处理中输出关于整体执行过程中由于嵌套内部过程调用而出现的错误:
参考文档:PLSQL实例精讲、