Oracle raise_application_error错误号最佳实践

时间:2016-03-29 21:16:27

标签: oracle error-handling

我对提高应用程序错误的错误代码(-20000到-20999)有疑问。

  1. 我们是否可以在PLSQL代码中的多个位置使用相同的错误代码(例如-20000)?

  2. 如果我们可以在所有地方使用相同的错误代码,为什么我们有1000个代码?

  3. 在提高应用程序错误中使用错误代码的最佳做法是什么?

  4. 示例代码:

    create table t(id number primary key);
    
    declare
    begin
      insert into t(id) values (1);
      insert into t(id) values (1);
      commit;
    exception 
      when dup_val_on_index 
      then 
        raise_application_error(-20000, 'Cannot Insert duplicates');
      when others 
      then
        raise_application_error(-20000, sqlcode||'-'||sqlerrm);
    end;
    

4 个答案:

答案 0 :(得分:3)

每次都可以使用相同的错误代码吗?肯定。

你应该吗?几乎肯定不是。如果Oracle针对出错的每一个可能的事情提出了ORA-00001错误 - 主键违规,外键违规,意外内部错误,表空间空间不足,权限错误等等,那将会很烦人。 - 因为这使得开发人员更难以处理他们可能遇到的错误并传播他们无法做到的错误。你必须做一些事情,比如解析错误字符串的文本,找出出错的地方,并弄清楚它是否是你能处理的错误。并且天堂禁止Oracle更改错误消息的文本或您的解析器导致您误解错误消息。同样,如果您的代码为遇到的每个可能的问题投入相同的错误代码,通常会很烦人。

如果您要使用自定义错误代码,它应该传达超出Oracle错误代码提供的内容。例如,使用when others将好的,可用的Oracle错误消息和错误堆栈转换为无意义的用户定义错误是没有意义的。但是,如果ORA-20001错误表明foo已经存在,并且ORA-20002错误表明当您有一个处理的应用程序时已经存在bar,那么这可能是完全合理的。很多foo和bar以及那些错误对于用户来说比通用的重复键错误更有意义。

答案 1 :(得分:3)

  1. 我们是否可以仅在PLSQL代码中的多个位置使用相同的错误代码(例如-20000)?
  2. 正如Justin所说,你当然可以这样做 - 只需使用一个代码。但它很可能导致混乱。我已经看到它完成了,通常在这种情况下,开发人员只需将所有关键信息嵌入到消息中,甚至包括代码(例如,他们可能已经使用了自己的错误代码,这些代码超出了可接受的范围)。

    我建议您遵循Oracle的主导:为应用程序的某些区域分配范围,然后在部件的该部分发生特定于应用程序的错误时使用范围内的错误代码。

    1. 如果我们可以在所有地方使用相同的错误代码,为什么我们有1000个代码?
    2. 见上文。

      1. 在提高应用程序错误中使用错误代码的最佳做法是什么?
      2. 创建一个表格,您可以在其中注册"使用的错误代码以及消息。然后开发人员可以查看"他们的"错误已经注册,可以重复使用。或者,更有可能的是,他们会注册一个新的错误代码和消息。

        无论哪种方式,您都有一个中心点来组织代码,并希望最大限度地减少使用相同错误代码的两个开发人员的更改。

        这是一个执行我上面建议的脚本,以及一个实用程序来生成一个包,其中定义了所有错误并且可用于"软编码"参考

        CREATE TABLE msg_info (
           msgcode INTEGER,
           msgtype VARCHAR2(30),
           msgtext VARCHAR2(2000),
           msgname VARCHAR2(30),
           description VARCHAR2(2000)
           );
        
        CREATE OR REPLACE PACKAGE msginfo
        IS
           FUNCTION text (
              code_in IN INTEGER
            , type_in IN VARCHAR2
            , use_sqlerrm IN BOOLEAN := TRUE
           )
              RETURN VARCHAR2;
        
           FUNCTION name (code_in IN INTEGER, type_in IN VARCHAR2)
              RETURN VARCHAR2;
        
           PROCEDURE genpkg (
              NAME_IN IN VARCHAR2
            , oradev_use IN BOOLEAN := FALSE
            , to_file_in IN BOOLEAN := TRUE
            , dir_in IN VARCHAR2 := 'DEMO' -- UTL_FILE directory
            , ext_in IN VARCHAR2 := 'pkg'
           );
        END;
        /
        
        CREATE OR REPLACE PACKAGE BODY msginfo
        IS
           FUNCTION msgrow (code_in IN INTEGER, type_in IN VARCHAR2)
              RETURN msg_info%ROWTYPE
           IS
              CURSOR msg_cur
              IS
                 SELECT *
                   FROM msg_info
                  WHERE msgtype = type_in AND msgcode = code_in;
        
              msg_rec   msg_info%ROWTYPE;
           BEGIN
              OPEN msg_cur;
              FETCH msg_cur INTO msg_rec;
              CLOSE msg_cur;
              RETURN msg_rec;
           END;
        
           FUNCTION text (
              code_in IN INTEGER
            , type_in IN VARCHAR2
            , use_sqlerrm IN BOOLEAN := TRUE
           )
              RETURN VARCHAR2
           IS
              msg_rec   msg_info%ROWTYPE   := msgrow (code_in, type_in);
           BEGIN
              IF msg_rec.msgtext IS NULL AND use_sqlerrm
              THEN
                 msg_rec.msgtext := SQLERRM (code_in);
              END IF;
        
              RETURN msg_rec.msgtext;
           END;
        
           FUNCTION NAME (code_in IN INTEGER, type_in IN VARCHAR2)
              RETURN VARCHAR2
           IS
              msg_rec   msg_info%ROWTYPE   := msgrow (code_in, type_in);
           BEGIN
              RETURN msg_rec.msgname;
           END;
        
           PROCEDURE genpkg (
              NAME_IN IN VARCHAR2
            , oradev_use IN BOOLEAN := FALSE
            , to_file_in IN BOOLEAN := TRUE
            , dir_in IN VARCHAR2 := 'DEMO'
            , ext_in IN VARCHAR2 := 'pkg'
           )
           IS
              CURSOR exc_20000
              IS
                 SELECT *
                   FROM msg_info
                  WHERE msgcode BETWEEN -20999 AND -20000 AND msgtype = 'EXCEPTION';
        
              -- Send output to file or screen?
              v_to_screen   BOOLEAN         := NVL (NOT to_file_in, TRUE);
              v_file        VARCHAR2 (1000) := name_in || '.' || ext_in;
        
              -- Array of output for package
              TYPE lines_t IS TABLE OF VARCHAR2 (1000)
                 INDEX BY BINARY_INTEGER;
        
              output        lines_t;
        
              -- Now pl simply writes to the array.
              PROCEDURE pl (str IN VARCHAR2)
              IS
              BEGIN
                 output (NVL (output.LAST, 0) + 1) := str;
              END;
        
              -- Dump to screen or file.
              PROCEDURE dump_output
              IS
              BEGIN
                 IF v_to_screen
                 THEN
                    FOR indx IN output.FIRST .. output.LAST
                    LOOP
                       DBMS_OUTPUT.put_line (output (indx));
                    END LOOP;
                 ELSE
                    -- Send output to the specified file.
                    DECLARE
                       fid   UTL_FILE.file_type;
                    BEGIN
                       fid := UTL_FILE.fopen (dir_in, v_file, 'W');
        
                       FOR indx IN output.FIRST .. output.LAST
                       LOOP
                          UTL_FILE.put_line (fid, output (indx));
                       END LOOP;
        
                       UTL_FILE.fclose (fid);
                    EXCEPTION
                       WHEN OTHERS
                       THEN
                          DBMS_OUTPUT.put_line (   'Failure to write output to '
                                                || dir_in
                                                || '/'
                                                || v_file
                                               );
                          UTL_FILE.fclose (fid);
                    END;
                 END IF;
              END dump_output;
           BEGIN
              /* Simple generator, based on DBMS_OUTPUT. */
              pl ('CREATE OR REPLACE PACKAGE ' || NAME_IN);
              pl ('IS ');
        
              FOR msg_rec IN exc_20000
              LOOP
                 IF exc_20000%ROWCOUNT > 1
                 THEN
                    pl (' ');
                 END IF;
        
                 pl ('   exc_' || msg_rec.msgname || ' EXCEPTION;');
                 pl (   '   en_'
                     || msg_rec.msgname
                     || ' CONSTANT INTEGER := '
                     || msg_rec.msgcode
                     || ';'
                    );
                 pl (   '   PRAGMA EXCEPTION_INIT (exc_'
                     || msg_rec.msgname
                     || ', '
                     || msg_rec.msgcode
                     || ');'
                    );
        
                 IF oradev_use
                 THEN
                    pl ('   FUNCTION ' || msg_rec.msgname || ' RETURN INTEGER;');
                 END IF;
              END LOOP;
        
              pl ('END ' || NAME_IN || ';');
              pl ('/');
        
              IF oradev_use
              THEN
                 pl ('CREATE OR REPLACE PACKAGE BODY ' || NAME_IN);
                 pl ('IS ');
        
                 FOR msg_rec IN exc_20000
                 LOOP
                    pl ('   FUNCTION ' || msg_rec.msgname || ' RETURN INTEGER');
                    pl ('   IS BEGIN RETURN en_' || msg_rec.msgname || '; END;');
                    pl ('   ');
                 END LOOP;
        
                 pl ('END ' || NAME_IN || ';');
                 pl ('/');
              END IF;
        
              dump_output;
           END;
        END;
        /
        
        /* Sample data to be used in package generation. */
        
        BEGIN
           INSERT INTO msg_info
             VALUES (-20100, 'EXCEPTION', 'Balance too low', 'bal_too_low'
                   , 'Description');
           INSERT INTO msg_info
             VALUES (-20200, 'EXCEPTION', 'Employee too young', 'emp_too_young'
                   , 'Description');
        
           COMMIT;
        END;
        /
        

答案 2 :(得分:1)

  

1)我们是否可以仅针对不同的错误使用相同的错误代码(例如-20000)   PLSQL代码中多个地方的场景?

你可以。然而,它并不总是有用 - 请继续阅读。

  

2)如果我们可以在所有地方使用相同的错误代码,为什么我们有1000   码?

要区分不同的错误条件 - 请继续阅读。

  

3)在Raise Application中使用错误代码的最佳做法是什么   错误?

定义一些内部标准,并继续关注它 - 继续阅读。

那么,用户定义的错误有什么意义呢?它们非常适合显示业务逻辑问题(例如,新添加的客户似乎已有950年历史,或者未来出生),而标准Oracle错误显示存储逻辑问题(例如,指未知的客户)。

根据您的应用程序模型,您可能在数据库之外实现业务逻辑,或者您可能更喜欢利用PL / SQL的强大功能来实现数据库中的业务逻辑。

但这并不太有用:

EXCEPTION 
When Dup_val_on_index 
then 
Raise_application_error(-20000,'Cannot Insert duplicates');
WHEN OTHERS 
THEN
Raise_application_error(-20000,SQLCODE||'-'||SQLERRM);
end;

首先,您可能会问自己,如果出现错误,您会怎么做?好吧,你可以打印一个漂亮的调用堆栈到最终用户的脸 - 这是完全没用的。我会说你可以将所有可能的问题隔离成用户可以修复的东西(例如,如果你遇到一个独特的约束,选择一个不同的登录名),以及用户无法修复的错误(例如,指的是不存在的用户)因为编程错误。)

我认为以这种方式区分错误是有道理的。如果你认为这是合乎逻辑的,那么:

  • 将所有异常包装到自定义BusinessError中,例如: -20000如果用户可以做某事。使用错误消息提供有用的描述(即“请选择一个不同的登录名”而不是“不能插入重复项”) - 然后您的应用程序已经位于前5%。

  • 将所有其他异常包装为技术错误,例如-20001。如果出现意外的技术错误,您的应用程序可以执行以下操作:记录它,并向用户显示一个很好的“出错但我们正在尝试处理它”的消息。很少有用户可以阅读/或有兴趣阅读Oracle调用栈:)

  • 我并没有完全出售标准的oracle错误消息。例如,简单的约束违规错误可能非常神秘。我建议明确命名所有约束,并遵循一个良好的命名约定。在这种情况下,您不需要实际处理任何内置的Oracle错误 - 因为它们将转到应用程序代码,这将记录它们并向用户显示“出错”,因为它不是业务错误。

限制:

  • 如果您在PL / SQL数据库中实现了业务逻辑,那么此解决方案非常有效。当然,如果您更喜欢应用服务器业务逻辑,例如Hibernate,那么你就可以自己处理每一个违反约束的行为。

  • 如果您正在构建多语言应用程序,则此解决方案可能需要扩展。商业错误将具有有意义的,人性化的消息,并且使用一种语言。您可能需要以多种语言发送消息(并在GUI上选择正确的消息),或者在其上实现您自己的翻译文件。

有业务错误的示例(一个内部错误,dup_val_on_index被包装到业务错误,因为这是由错误的用户输入引起的)

procedure addUser(in_birthDate date, ) is
    pls_integer age=sysdate-in_birthDate;
begin
     if age>100 then
         raise_application_error(-20000,'Check birth date, you''re too old!');
     elsif age<0 then
         raise_application_error(-20000,'Birth date can not be in the future.');
     end if;
     insert into....
     commit;
exception  
    when dup_val_on_index then 
        raise_application_error(-20000,'Please pick a different login name!');
end;

您可以考虑创建一个抛出业务错误的过程:

CREATE OR REPLACE
Procedure throw (in_message varchar2) is
begin
    raise_application_error(-20999,in_message);
end;

这使您的代码更具可读性:

begin
     if age>100 then
         throw('Check birth date, you''re too old!');

答案 3 :(得分:0)

仅仅因为你可以做某事并不意味着你应该做点什么。就个人而言,作为一名顾问来消除其他人的混乱,我们(雇用枪支)讨厌在他们的代码中没有提供有用错误消息的开发人员。 Oracle做得非常好 - 大部分时间都是如此。在DUPS的情况下,如果在错误消息中,开发人员在他们的错误消息中添加了一行,那将是重复的值 - 这是一个概念。也许您不会将文本发送回最终用户,但应考虑使用dbms_system.ksdwrt - 将这些错误写入警报日志的proc。