효율적인 예외처리 방법
1. 시스템 예외인 경우는 OTHERS를 사용하자
모든 시스템 예외를 알 수 없기 때문에, 모든 시스템 예외를 잡아주는 OTHERS와 예외의 정보를 알려주는 SQLCODE, SQLERRM을 적극 사용하자.
2. 예외처리 루틴을 공통 모듈화하고, 발생된 예외 로그를 남기자
발생한 예외에 대한 로그 테이블을 만들어 예외가 발생할 때 만다 로그 테이블에 기록하자.
3. 사용자 정의 예외도 별도의 테이블을 미리 만들어 관리한다.
동일한 예외 코드의 중복을 막기위해서
예제
먼저, 예외 로그를 남길 테이블을 만든다.
1 2 3 4 5 6 7 8 9 | CREATE TABLE ERROR_LOG ( ERROR_SEQ NUMBER, -- 에러 시퀀스 PROG_NAME VARCHAR2(80), -- 프로그램명 ERROR_CODE NUMBER, -- 에러 코드 ERROR_MESSAGE VARCHAR2(300), -- 에러 메시지 ERROR_LINE VARCHAR2(100), -- 에러 라인 ERROR_DATE DATE DEFAULT SYSDATE -- 에러 발생일 ); | cs |
ERROR_LOG 테이블의 에러 시퀀스를 만든다.
1 2 3 4 5 6 7 | CREATE SEQUENCE ERROR_SEQ INCREMENT BY 1 START WITH 1 MINVALUE 1 MAXVALUE 999999 NOCYCLE NOCACHE; | cs |
ERROR_LOG 테이블에 데이터를 INSERT하는 프로시저를 만든다.
이 프로시저를 이용하면 다른 곳에서 예외 처리 후 로그를 남길 때, INSERT문을 작성하지 않고 이 프로시저를 실행시키면 된다.
1 2 3 4 5 6 7 8 9 10 11 12 13 | CREATE OR REPLACE PROCEDURE ERROR_LOG_PROC ( P_PROG_NAME ERROR_LOG.PROG_NAME%TYPE, P_ERROR_CODE ERROR_LOG.ERROR_CODE%TYPE, P_ERROR_MESSAGE ERROR_LOG.ERROR_MESSAGE%TYPE, P_ERROR_LINE ERROR_LOG.ERROR_LINE%TYPE ) IS BEGIN INSERT INTO ERROR_LOG (ERROR_SEQ, PROG_NAME, ERROR_CODE, ERROR_MESSAGE, ERROR_LINE) VALUES (ERROR_SEQ.NEXTVAL, P_PROG_NAME, P_ERROR_CODE, P_ERROR_MESSAGE, P_ERROR_LINE); COMMIT; END; | cs |
마지막으로, 테스트용 프로시저를 만든다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 | CREATE OR REPLACE PROCEDURE INS_EMP2_PROC ( P_EMP_NAME EMPLOYEES.EMP_NAME%TYPE, P_DEPARTMENT_ID DEPARTMENTS.DEPARTMENT_ID%TYPE, P_HIRE_MONTH VARCHAR2 ) IS VN_EMPLOYEE_ID EMPLOYEES.EMPLOYEE_ID%TYPE; VD_CURR_DATE DATE := SYSDATE; VN_CNT NUMBER := 0; EX_INVALID_DEPID EXCEPTION; PRAGMA EXCEPTION_INIT(EX_INVALID_DEPID, -20000); EX_INVALID_MONTH EXCEPTION; PRAGMA EXCEPTION_INIT(EX_INVALID_MONTH, -1843); V_ERR_CODE ERROR_LOG.ERROR_CODE%TYPE; V_ERR_MSG ERROR_LOG.ERROR_MESSAGE%TYPE; V_ERR_LINE ERROR_LOG.ERROR_LINE%TYPE; BEGIN SELECT COUNT(*) INTO VN_CNT FROM DEPARTMENTS WHERE DEPARTMENT_ID = P_DEPARTMENT_ID; IF VN_CNT = 0 THEN RAISE EX_INVALID_DEPID; END IF; IF SUBSTR(P_HIRE_MONTH, 5, 2) NOT BETWEEN '01' AND '12' THEN RAISE EX_INVALID_MONTH; END IF; SELECT MAX(EMPLOYEE_ID) + 1 INTO VN_EMPLOYEE_ID FROM EMPLOYEES; INSERT INTO EMPLOYEES (EMPLOYEE_ID, EMP_NAME, HIRE_DATE, DEPARTMENT_ID) VALUES (VN_EMPLOYEE_ID, P_EMP_NAME, TO_DATE(P_HIRE_MONTH || '01'), P_DEPARTMENT_ID); COMMIT; EXCEPTION WHEN EX_INVALID_DEPID THEN V_ERR_CODE := SQLCODE; V_ERR_MSG := '해당 부서가 없습니다.'; V_ERR_LINE := DBMS_UTILITY.FORMAT_ERROR_BACKTRACE; ROLLBACK; ERROR_LOG_PROC ('INS_EMP2_PROC', V_ERR_CODE, V_ERR_MSG, V_ERR_LINE); WHEN EX_INVALID_MONTH THEN V_ERR_CODE := SQLCODE; V_ERR_MSG := SQLERRM; V_ERR_LINE := DBMS_UTILITY.FORMAT_ERROR_BACKTRACE; ROLLBACK; ERROR_LOG_PROC ('INS_EMP2_PROC', V_ERR_CODE, V_ERR_MSG, V_ERR_LINE); WHEN OTHERS THEN V_ERR_CODE := SQLCODE; V_ERR_MSG := SQLERRM; V_ERR_LINE := DBMS_UTILITY.FORMAT_ERROR_BACKTRACE; ROLLBACK; ERROR_LOG_PROC ('INS_EMP2_PROC', V_ERR_CODE, V_ERR_MSG, V_ERR_LINE); END; | cs |
코드 길이가 좀 있지만, 찬찬히 보면 충분히 해석 가능한 길이다.
실제로 테스트를 진행해보자.
1 2 3 4 5 | EXEC INS_EMP2_PROC ('HONG', 1000, '201801'); EXEC INS_EMP2_PROC ('HONG', 100, '201813'); SELECT * FROM ERROR_LOG; | cs |
첫 번째 라인은 존재하지 않는 부서번호를, 두번째 라인은 2018년 13월을 매개변수로 넘겼다.
다음으로, 사용자 정의 예외목록 테이블을 만들고 그 예외들도 로그 테이블에 남겨보자.
사용자 정의 예외목록 테이블을 이용하면 사용자 정의 예외들을 효율적으로 관리할 수 있고, 코드의 중복을 줄일 수 있다.
사용자 정의 예외목록 테이블을 만들자. 기본키는 에러코드다.
1 2 3 4 5 6 7 | CREATE TABLE APP_USER_DEFINE_ERROR ( ERROR_CODE NUMBER, ERROR_MESSAGE VARCHAR2(300), CREATE_DATE DATE DEFAULT SYSDATE, PRIMARY KEY (ERROR_CODE) ); | cs |
그리고 사용할 예외 정보를 사용자 정의 예외목록 테이블에 입력한다.
1 2 3 4 | INSERT INTO APP_USER_DEFINE_ERROR (ERROR_CODE, ERROR_MESSAGE) VALUES (-1843, '지정한 월이 부적합합니다.'); INSERT INTO APP_USER_DEFINE_ERROR (ERROR_CODE, ERROR_MESSAGE) VALUES (-20000, '해당 부서가 없습니다.'); | cs |
이 전에 사용했던 예외 로그 기록 프로시저를 수정한다. (변경되는 내용은 굵은 글씨)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | CREATE OR REPLACE PROCEDURE ERROR_LOG_PROC ( P_PROG_NAME ERROR_LOG.PROG_NAME%TYPE, P_ERROR_CODE ERROR_LOG.ERROR_CODE%TYPE, P_ERROR_MESSAGE ERROR_LOG.ERROR_MESSAGE%TYPE, P_ERROR_LINE ERROR_LOG.ERROR_LINE%TYPE ) IS VN_ERROR_CODE ERROR_LOG.ERROR_CODE%TYPE := P_ERROR_CODE; VN_ERROR_MESSAGE ERROR_LOG.ERROR_MESSAGE%TYPE := P_ERROR_MESSAGE; BEGIN BEGIN SELECT ERROR_MESSAGE INTO VN_ERROR_MESSAGE FROM APP_USER_DEFINE_ERROR WHERE ERROR_CODE = VN_ERROR_CODE; EXCEPTION WHEN NO_DATA_FOUND THEN VN_ERROR_MESSAGE := P_ERROR_MESSAGE; END; INSERT INTO ERROR_LOG (ERROR_SEQ, PROG_NAME, ERROR_CODE, ERROR_MESSAGE, ERROR_LINE) VALUES (ERROR_SEQ.NEXTVAL, P_PROG_NAME, VN_ERROR_CODE, VN_ERROR_MESSAGE, P_ERROR_LINE); COMMIT; END; | cs |
테스트용 프로시저도 다음과 같이 수정한다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 | CREATE OR REPLACE PROCEDURE INS_EMP2_PROC ( P_EMP_NAME EMPLOYEES.EMP_NAME%TYPE, P_DEPARTMENT_ID DEPARTMENTS.DEPARTMENT_ID%TYPE, P_HIRE_MONTH VARCHAR2 ) IS VN_EMPLOYEE_ID EMPLOYEES.EMPLOYEE_ID%TYPE; VD_CURR_DATE DATE := SYSDATE; VN_CNT NUMBER := 0; EX_INVALID_DEPID EXCEPTION; PRAGMA EXCEPTION_INIT(EX_INVALID_DEPID, -20000); EX_INVALID_MONTH EXCEPTION; PRAGMA EXCEPTION_INIT(EX_INVALID_MONTH, -1843); V_ERR_CODE ERROR_LOG.ERROR_CODE%TYPE; V_ERR_MSG ERROR_LOG.ERROR_MESSAGE%TYPE; V_ERR_LINE ERROR_LOG.ERROR_LINE%TYPE; BEGIN SELECT COUNT(*) INTO VN_CNT FROM DEPARTMENTS WHERE DEPARTMENT_ID = P_DEPARTMENT_ID; IF VN_CNT = 0 THEN RAISE EX_INVALID_DEPID; END IF; IF SUBSTR(P_HIRE_MONTH, 5, 2) NOT BETWEEN '01' AND '12' THEN RAISE EX_INVALID_MONTH; END IF; SELECT MAX(EMPLOYEE_ID) + 1 INTO VN_EMPLOYEE_ID FROM EMPLOYEES; INSERT INTO EMPLOYEES (EMPLOYEE_ID, EMP_NAME, HIRE_DATE, DEPARTMENT_ID) VALUES (VN_EMPLOYEE_ID, P_EMP_NAME, TO_DATE(P_HIRE_MONTH || '01'), P_DEPARTMENT_ID); COMMIT; EXCEPTION WHEN EX_INVALID_DEPID THEN V_ERR_CODE := SQLCODE; V_ERR_LINE := DBMS_UTILITY.FORMAT_ERROR_BACKTRACE; ROLLBACK; ERROR_LOG_PROC ('INS_EMP2_PROC', V_ERR_CODE, V_ERR_MSG, V_ERR_LINE); WHEN EX_INVALID_MONTH THEN V_ERR_CODE := SQLCODE; V_ERR_LINE := DBMS_UTILITY.FORMAT_ERROR_BACKTRACE; ROLLBACK; ERROR_LOG_PROC ('INS_EMP2_PROC', V_ERR_CODE, V_ERR_MSG, V_ERR_LINE); WHEN OTHERS THEN V_ERR_CODE := SQLCODE; V_ERR_MSG := SQLERRM; V_ERR_LINE := DBMS_UTILITY.FORMAT_ERROR_BACKTRACE; ROLLBACK; ERROR_LOG_PROC ('INS_EMP2_PROC', V_ERR_CODE, V_ERR_MSG, V_ERR_LINE); END; | cs |
사용자 정의 예외의 경우, 예외 메시지가 이미 테이블상에 존재하기 때문에 따로 매개변수에 초기화 하는 작업이 필요없다.
다시 예외를 발생시켜보자.
1 2 | EXEC INS_EMP2_PROC ('HONG', 1000, '201801'); EXEC INS_EMP2_PROC ('HONG', 100, '201813'); | cs |
그리고 예외 로그 기록 테이블을 조회하면 다음과 같은 결과가 나온다.
1 2 | SELECT * FROM ERROR_LOG; | cs |
1,2행은 사용자 정의 예외 테이블을 만들기 전, 3,4행은 만든 이후이다.
'📃 DATABASE > ORACLE' 카테고리의 다른 글
[PL/SQL] 커서(CURSOR) (1) : 명시적 커서와 묵시적 커서 (4) | 2018.02.01 |
---|---|
[PL/SQL] 트랜잭션 (0) | 2018.01.29 |
[PL/SQL] 예외처리(2) : 시스템 예외와 사용자정의 예외 (0) | 2018.01.25 |
[PL/SQL] 예외처리 (1) : 예외처리란? (0) | 2018.01.24 |
[PL/SQL] 프로시저(PROCEDURE) (0) | 2018.01.23 |