/******** 패키지 스펙 선언부 *********/ CREATEOR REPLACE PACKAGE hr_pkg IS
FUNCTION fn_get_emp_name (pn_employee_id IN NUMBER) RETURN VARCHAR2;
PROCEDURE new_emp_proc(ps_emp_name IN VARCHAR2 , pd_hire_date IN VARCHAR2); END hr_pkg;
/******** 패키지 바디 선언부 *********/ CREATEOR REPLACE PACKAGE BODY hr_pkg IS
FUNCTION fn_get_emp_name (pn_employee_id IN NUMBER) RETURN VARCHAR2 IS vs_emp_name employees.emp_name%TYPE; BEGIN SELECT emp_name INTO vs_emp_name FROM employees WHERE employee_id = pn_employee_id; RETURN NVL(vs_emp_name , '해당사원 없음'); END fn_get_emp_name;
PROCEDURE new_emp_proc (ps_emp_name IN VARCHAR2 , pd_hire_date IN VARCHAR2 ) IS vn_emp_id employees.employee_id%TYPE; vd_hire_date DATE := TO_DATE(pd_hire_date , 'YYYY-MM-DD'); BEGIN SELECT NVL(max(employee_id),0) +1 INTO vn_emp_id FROM employees; INSERTINTO employees (employee_id , emp_name , hire_date , create_date , update_date ) VALUES (vn_emp_id , ps_emp_name , NVL(vd_hire_date ,SYSDATE) , SYSDATE , SYSDATE ); COMMIT; EXCEPTION WHEN OTHERS THEN DBMS_OUTPUT.PUT_LINE(SQLERRM); ROLLBACK; END new_emp_proc;
END hr_pkg;
작성한 프로시저는 ‘패키지명.서브프로그램명’ 으로 호출이 가능하다.
1 2
SELECT hr_pkg.fn_get_emp_name(171) FROM DUAL; EXEC hr_pkg.new_emp_proc('chansoo Kim','2022-05-31');
패키지 데이터
패키지에는 상수 , 변수 , 커서, 레코드 ,컬렉션, 예외를 자바의 멤버필드처럼 가질 수 있다. 이를 패키지내에 데이터를 담아둔다고 한다.
패키지 데이터의 생존 주기는 세션 단위이다. 즉 세션이 살아있는 동안 그 값이 메모리 상에 유지된다.
패키지 데이터는 ‘선언부(스펙)’ 와 ‘본문(바디)’ 에 모두 선언이 가능한데, 선언부에 선언할 경우 패키지외부에서 접근이 가능한 반면 바디에 선언한 경우 패키지 내부에서만 접근이 가능하다.
패키지 선언부에 위치한 데이터를 Public Item , 바디부에 위치한 데이터를 Private Item이라고 부른다.
(private - 패키지 body에 선언 / public - 패키지 spec에 선언)
1 2 3 4 5 6 7 8 9 10 11 12
CREATEOR REPLACE PACKAGE ch12_var IS /*상수 선언*/ c_test CONSTANT VARCHAR2(10) :='TEST'; /*변수 선언*/ v_test VARCHAR2(10); END ch12_var;
CREATEOR REPLACE PACKAGE BODY ch12_var IS /*외부에서 접근 불가능한 변수를 선언하고자할떄는 바디에 데이터를 선언한다. */ c_test_body CONSTANT VARCHAR2(10):='CONSTANT_BODY'; v_test_body VARCHAR2(10); END ch12_var;
마찬가지로 선언한 상수나 변수도 서브프로그램 처럼 ‘패키지명.변수명’ , ‘패키지명.상수명’ 형태로 참조한다.
c_test_body CONSTANT VARCHAR2(10):='CONSTANT_BODY'; v_test_body VARCHAR2(10); /*Getter Function*/ FUNCTION fn_get_value RETURN VARCHAR2 IS BEGIN RETURN NVL(v_test_body , 'NULL이다.'); END fn_get_value; /*Setter Procedure*/ PROCEDURE sp_set_value (ps_value VARCHAR2) IS BEGIN v_test_body := ps_value; END sp_set_value; END ch12_var;
커서
변수나 상수와 마찬가지로 패키지내에 선언된 커서도 세션이 살아있는 동안 유지된다. 패키지 선언부(헤더)에 커서 구현부까지 선언한 경우와 헤더에는 커서 타입만 명시하고 , 바디에는 커서 구현부까지 명시하는 방법 2가지가 존재한다. 두 방법 모두 외부에서 접근 가능하며 , ‘패키지명.커서명’ 형태로 참조할 수 있다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
/*패키지 헤더에 커서 구현부까지 선언 */ CREATEOR REPLACE PACKAGE ch12_cur_pkg IS CURSOR pc_empdep_cur (dep_id IN DEPARTMENTS.DEPARTMENT_ID%TYPE) IS SELECT a.employee_id , a.emp_name , b.department_name FROM employees a , departments b WHERE a.department_id = dep_id AND a.department_id = b.department_id; END ch12_cur_pkg;
BEGIN FOR rec IN ch12_cur_pkg.pc_empdep_cur(30) LOOP DBMS_OUTPUT.PUT_LINE(rec.emp_name ||'-'|| rec.department_name); END LOOP; END;
이때 헤더에 커서타입만을 명시할때는 커서가 반환하는 데이터 타입을 RETURN 절에 명시해야 하는데, 이떄는 사용자 정의 record 타입 또는 %ROWTYPE 구문을 사용할 수 있다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14
/*패키지 헤더에는 타입만 선언하고 , 바디에 구현부까지 선언 */ CREATEOR REPLACE PACKAGE ch12_cur_pkg IS CURSOR PC_EMPDEP_CUR (dep_id IN DEPARTMENTS.DEPARTMENT_ID%TYPE) RETURN departments%ROWTYPE; END ch12_cur_pkg;
CREATEOR REPLACE PACKAGE BODY ch12_cur_pkg IS CURSOR PC_EMPDEP_CUR (dep_id IN DEPARTMENTS.DEPARTMENT_ID%TYPE) RETURN departments%ROWTYPE IS SELECT* FROM departments dep WHERE dep.department_id = dep_id; END ch12_cur_pkg;
사용자 레코드 타입을 반환타입으로 지정하는 경우도 큰차이는 없다 다만 말 그대로 record 타입을 지정하고, 그에 맞는 데이터 타입을 반환해주면 된다.
1 2 3 4 5 6 7 8 9 10 11
CREATEOR REPLACE PACKAGE EXAMPLE IS /*사용자 정의 레코드 타입 선언*/ TYPE EMP_DEP_RT IS RECORD ( emp_id employees.employee_id%TYPE , emp_name employees.emp_name%TYPE, job_title jobs.job_title%TYPE ); CURSOR pc_empdep2_cur (p_job_id IN jobs.job_id%TYPE) /*사용자 정의 레코드 타입을 반환 */ RETURN EMP_DEP_RT; END EXAMPLE;
패키지에서 사용 시 주의점
패키지에서 선언한 커서는 변수처럼 DB 세션이 살아있는 동안 공유된다. 따라서 커서 닫는 것을 누락하게 되면 같은 세션에서 해당 커서를 재사용할 때 문제가 발생한다.
패키지에서 Collection 타입 사용
레코드와 Collection 모두 데이터 타입에 속하므로 패키지에서 사용이 가능하다.
아래의 예시는 컬렉션 타입 , 정확히는 컬렉션 타입 중에 중첩 테이블을 선언하고, 반복문을 돌면서 컬렉션에 값을 추가하는 프로시저를 패키지에서 선언하고 있다.
CREATEOR REPLACE PACKAGE ch12_col_pkg IS /* 컬렉션 타입 선언 */ TYPE NT_DEP_NAME ISTABLEOF VARCHAR2(30); dep_name NT_DEP_NAME := NT_DEP_NAME(); /* 컬렉션에 값을 추가하는 프로시저 */ PROCEDURE make_dep_proc (p_par_id IN NUMBER); END ch12_col_pkg;
CREATEOR REPLACE PACKAGE BODY ch12_col_pkg IS PROCEDURE make_dep_proc(p_par_id IN NUMBER) IS BEGIN FOR rec IN ( SELECT department_name FROM departments WHERE parent_id = p_par_id ) LOOP /*dep_name 컬렉션 맨 끝에 NULL인 요소 하나 추가*/ dep_name.EXTEND(); /*dep_name의 요소의 총수에 해당되는 값을 NULL에서 department_name으로 업데이트*/ dep_name(dep_name.COUNT()) := rec.department_name; END LOOP; END make_dep_proc; END ch12_col_pkg;
패키지 데이터가 저장된 공간 - UGA (User Global Area)
패키지에 변수,상수,커서,예외등을 선언하면 DB세션이 살아있는 동안 그 값이 공유되는데, (DB세션 내에서) 이때 패키지 데이터가 저장되어 있는 공간을 UGA (User Global Area) 라고 한다.
UGA란 오라클 내부에서 사용하는 내부 메모리 공간으로 주로 세션 관련 정보를 가지고 있다.
1세션당 1 UGA 메모리 공간이 할당된다.
만약 DB 사용자수가 많아져서 세션 수가 증가하면 UGA 메모리 공간을 많이 차지한다. 따라서 불필요하게 패키지 데이터를 세션별로 공유하고 싶지 않을때 , PRAGMA SERIALLY_REUSABLE 옵션을 주어 호출이 시작되고 끝날떄까지만 패키지 데이터의 생명주기를 관리할 수 있다.
세션이 아닌 호출 단위별로 패키지 데이터 사용 - PRAGMA SERIALLY_REUSABLE 옵션
PRAGMA SERIALLY_REUSABLE 사용 방법은 헤더와 바디에 각각 선언해주면 된다.
1 2 3 4 5 6 7 8 9
CREATEOR REPLACE PACKAGE TEST IS PRAGMA SERIALLY_REUSABLE; /*패키지 헤더*/ END TEST;
CREATEOR REPLACE PACKAGE BODY TEST IS PRAGMA SERIALLY_REUSABLE; /*패키지 바디*/ END TEST;
패키지의 함수, 프로시저 Overloading 기능
오라클에서도 객체 지향 프로그래밍의 오버로딩과 동일한 개념을 패키지에서 선언한 함수와 프로시저에 한해 지원해주고 있다.
동일한 이름이지만 매개변수 타입이나 개수가 다르면 여러개의 함수 또는 프로시저를 패키지내에서 선언할 수 있다.
1 2 3 4 5 6 7
CREATEOR REPLACE PACKAGE overload_pkg IS /*매개변수로 사번을 받는 경우*/ PROCEDURE get_dep_nm_proc( p_emp_id IN NUMBER); /*매개변수로 사원명을 받는 경우*/ PROCEDURE get_dep_nm_proc( p_emp_name IN VARCHAR2); END overload_pkg;