9.3 创建并抛出命名的自定义异常
问题
你想在某个特定事件发生时向应用程序的用户发出一些警告信息。事件本身不会抛出Oracle内部异常,因为这个异常是属于应用程序的特定异常。所以,你需要把这个事件与自定义异常关联起来,这样一来,不论事件在什么时候发生异常都可以抛出。
解决方案
声明一个用户定义的命名异常,并把它与你想抛出的异常事件相关联。下面的例子在代码块中声明并抛出一个用户定义的异常。异常抛出时,应用程序的控制权就会传递给控制用户命名异常的异常处理程序。
CREATE OR REPLACE PROCEDURE salary_increase(emp_id IN NUMBER,
pct_increase IN NUMBER) AS
salary employees.salary%TYPE;
max_salary jobs.max_salary%TYPE;
INVALID_INCREASE EXCEPTION;
BEGIN
SELECT salary, max_salary
INTO salary, max_salary
FROM employees, jobs
WHERE employee_id = emp_id
AND jobs.job_id = employees.employee_id;
IF (salary + (salary * pct_increase)) <= max_salary THEN
UPDATE employees
SET salary = (salary + (salary * pct_increase))
WHERE employee_id = emp_id;
DBMS_OUTPUT.PUT_LINE('SUCCESSFUL SALARY INCREASE FOR EMPLOYEE #: ' ||
emp_id ||
'. NEW SALARY = ' || salary + (salary * pct_increase));
ELSE
RAISE INVALID_INCREASE;
END IF;
EXCEPTION
WHEN NO_DATA_FOUND THEN
DBMS_OUTPUT.PUT_LINE('UNSUCCESSFUL INCREASE, NO EMPLOYEE RECORD FOUND ' ||
'FOR THE GIVEN ID');
WHEN INVALID_INCREASE THEN
DBMS_OUTPUT.PUT_LINE('UNSUCCESSFUL INCREASE. YOU CANNOT INCREASE THE ' ||
'EMPLOYEE SALARY BY ' || pct_increase ||
'PERCENT...PLEASE ENTER ' ||
'A SMALLER INCREASE AMOUNT TO TRY AGAIN');
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE('UNSUCCESSFUL INCREASE. AN UNCKNOWN ERROR HAS '||
'OCCURRED, ' ||
'PLEASE TRY AGAIN OR CONTACT ADMINISTRATOR' || pct_increase);
END;
/
从代码中可以看出,异常块可以容纳一个或多个处理程序。命名的用户异常在过程的声明部分声明,并且这个异常可以在代码块的任何地方抛出。
注意 在真实的应用程序中,异常应该在OTHERS处理程序中手动引发。关于如何确定异常起因的更多内容请参考9.4节。
原理分析
一个PL/SQL应用程序可以包含任意数量的自定义异常。开发人员声明了他们自己的异常时,这些异常就称为用户定义异常。用户定义异常必须在包、函数、过程或匿名代码块的声明部分声明。要声明一个异常,可以使用下面的语法:
exception_name EXCEPTION;
对于异常的命名,只要符合标准的命名规范并且不同于内部定义的异常名称,都可以使用。规范的异常名称应该全部采用大写,但小写也可以使用,因为PL/SQL语言不区分大小写。
如果想抛出你自己的异常,需要在RAISE关键字后面加上你要抛出的异常名称。代码执行到RAISE语句时,控制权就会传递给与命名异常最为匹配的异常处理程序。如果找不到与抛出异常相匹配的异常处理程序,控制权就会传入OTHERS处理程序,但前提是存在OTHERS处理程序。而在最糟糕的情况下,如果没有任何异常处理程序能够与RAISE语句中抛出的异常名称相匹配,并且代码中也没有编写OTHERS处理程序,控制权就会被传回外层代码块、调用当前程序的代码块或是宿主环境。
关于RAISE语句,除了上面提到的一种用法之外,还有别的用法。比如抛出一个在其他包中声明的异常。为此,需要在异常的前面加上完整的包名作为前缀。另外,RAISE语句也可以独立使用,用来重新抛出一个异常。
从这个案例的解决方案中可以看出,捕获命名的用户异常与捕获内部定义异常完全一样。都是编写一个WHEN…THEN子句,中间放你想捕获的异常名字。异常抛出时,会执行对应异常处理程序中包含的语句。