본문 바로가기
DataBase/Oracle

패키지(Package)

by 글발 2023. 11. 3.
728x90
반응형

패키지

패키지란 단어 뜻에서 어느정도 유추가 가능하다.

뭘 모아놓았다는 느낌인데 

실제로 여러가지 뭐 다 떄려박을 수 있다.

물론 관련이 있는 것들을 한 곳에 모아야되는건 당연하다.

전문성 있게 말하자면,

논리적 연관성이 있는 PL/SQL 타입, 변수, 상수, 서브 프로그램, 커서, 예외 등의 항목을 묶어 놓은 객체이다.

패키지는 컴파일 후에 DB에 저장이 되고

다른 곳에서(다른 패키지나 프로시저, 외부 프로그램 등) 패키지의 항목을 참조, 공유, 실행할 수 있다.

그렇다면 패키지의 왜 쓸까?

패키지의 장점

패키지의 장점 4가지 정도만 정리해보자.

  1. 모듈화

패키지의 가장 큰 장점이다.

업무적으로 연관성이 있는 서브 프로그램이나 변수, 상수, 커서, 사용자 정의 타입(함수, 프로시저 등)들을

하나의 패키지에 담아두면 이해하기도 관리하기도 쉽다.

함수나 프로시저 개수가 적으면 눈에 띄지 않겠지만

시스템이 확장되고 프로그램 개수가 늘어난다면 패키지로 묶어 놓은 효과는 커진다.

 

  2. 프로그램 설계의 용이성

패키지는 선언부(스펙)와 본문(바디)의 두 부분으로 구성된다.

선언부는 패키지에서 사용할 각종 변수, 상수, 타입, 커서, 함수, 프로시저 등을 선언하는 부분이고,

본문은 함수와 프로시저를 구현한 부분이다.

그런데 패키지는 선언부만 있어도 컴파일한 뒤 저장이 가능하다.

즉 본문은 나중에 작성해도 된다는 뜻이다.

여기서의 장점은 프로젝트를 여러 팀이 뭉쳐서 수행을 할 때

다른 팀들에서 사용해야할 공통 모듈 설계에서

선언부만 작성해 놓으면(프로시저의 매개변수의 유형과 개수, 처리할 내용만 협의)

다른 팀들에서 기다릴 필요 없이(컴파일 오류 없이) 진도를 나갈 수 있다.

뿐만 아니라 나중에 공통 모듈의 프로시저의 구현부가 완성이 되어도

다른 팀들의 패키지나 프로시저를 다시 컴파일할 필요가 없다.

 

  3. 캡슐화

캡슐화는 필요한 부분만 외부에 공개하고 그 외 세부적인 로직은 숨긴다는(접근을 차단하는) 개념이다.

객체지향 프로그래밍에서 주로 사용되는 개념이다.

패키지에서는 선언부는 외부에 공개되지만

패키지에 속한 커서, 함수, 프로시저 등의 세부 구현 내용이 담겨있는 본문 부분은 외부에서 볼 수 없다.

즉 정보은닉이 가능한 것이며 외부 모듈에 영향을 주지 않고도 패키지 본문 내용은 언제든지 수정 가능하다.

 

  4. 성능

패키지를 사용하여 보다 향상된 성능을 기대할 수 있다.

패키지에 있는 서브 프로그램을 호출하면 일단 해당 패키지 전체를 메모리에 올려놓는다.

이후 계속 호출하면 메모리에 올라가 잇는 상태이므로 더 나은 성능을 보인다.

 

패키지 사용

이제 패키지를 만들어서 사용해보자.

사원과 관련된 내용을 처리하는 패키지를 hr_pkg로 만들고 그 구성에는 세가지로 아래와 같다.

- fn_get_emp_name : 사번을 전달받아 이름을 반환하는 함수

- new_emp_proc : 신규사원을 등록하는 프로시저

- retire_emp_proc : 퇴사한 사원을 처리하는 프로시저

-- hr_pkg 선언부
CREATE OR 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);

-- 퇴사 사원 처리
PROCEDURE retire_emp_proc(pn_employee_id IN NUMBER);

END hr_pkg;

패키지의 선언부만 작성했는데도 이렇게 컴파일이 되는 모습이다.

이제 패키지 본문을 작성해보자.

CREATE OR 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
            -- 신규 사원의 사번 = 최대 사번 + 1
            SELECT NVL(MAX(employee_id), 0) + 1
              INTO vn_emp_id
              FROM employees;
              
            INSERT INTO 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;
        
        -- 퇴사 사원 처리
        PROCEDURE retire_emp_proc(pn_employee_id IN NUMBER)
        IS
            vn_cnt NUMBER := 0;
            e_no_data EXCEPTION;
            
            BEGIN
                -- 퇴사한 사원은 사원 테이블에서 삭제하지 않고 일단 퇴사일자(retire_date)를 NULL에서 현재일자로 갱신
                UPDATE employees
                   SET retire_date = SYSDATE
                 WHERE employee_id = pn_employee_id
                   AND retire_date IS NULL;
                
                -- UPDATE된 건수를 가져옴
                vn_cnt := SQL%ROWCOUNT;
                
                -- 갱신된 건수가 없으면 사용자 예외처리
                IF vn_cnt = 0 THEN
                    RAISE e_no_data;
                END IF;
                
                COMMIT;
                
                EXCEPTION WHEN e_no_data THEN
                    DBMS_OUTPUT.PUT_LINE(pn_employee_id || '에 해당되는 퇴사처리할 사원이 없습니다.');
                    ROLLBACK;
                WHEN OTHERS THEN
                    DBMS_OUTPUT.PUT_LINE(SQLERRM);
                    ROLLBACK;
        END retire_emp_proc;
END hr_pkg;

패키지 본문도 컴파일 하였고,

이제 패키지를 사용해보자.

패키지는 '패키지명.서브프로그램명' 형태로 사용한다.

먼저, 사원명을 가져오는 함수를 호출해보자.

SELECT hr_pkg.fn_get_emp_name(171) FROM DUAL;

정상적으로 사원명이 호출 되었다.

이번에는 신규로 사원을 등록해보자.

신규사원 등록은 사원 명과 입사일자가 필요한데, 입사일자를 누락하면 현재일자로 입력된다.

EXEC hr_pkg.new_emp_proc('Leo', '2023-11-03');

성공적으로 완료됐다니까 실제로 확인해보자.

SELECT employee_id, emp_name, hire_date, retire_date
  FROM employees
 WHERE emp_name LIKE '%Leo%';

정상적으로 신규로 입력이 완료 되었음을 확인할 수 있다.

이제 다시 이 사원을 퇴사처리해보자.

퇴사처리를 하면 해당 사원 데이터를 삭제하는 것이 아닌 퇴사일자(retire_date) 값을 갱신한다.

EXEC hr_pkg.retire_emp_proc(207);

성공적으로 완료됐다고 알려준다.

확인해보자.

SELECT employee_id, emp_name, hire_date, retire_date
  FROM employees
 WHERE emp_name LIKE '%Leo%';

 

이렇게 패키지를 만들어놓으면 타 프로그램에서도 호출하여 사용할 수 있다.

'DataBase > Oracle' 카테고리의 다른 글

레코드(RECORD)  (1) 2023.11.01
묵시적 커서(CURSOR)  (0) 2023.10.31
CUBE  (0) 2023.10.31
ROLLUP  (0) 2023.10.30
WITH절  (1) 2023.10.16