Um package é uma coleção de objetos PL/SQL agrupados logicamente sob o nome de pacote. Os packages podem incluir procedures, functions, cursores, declarações, tipos e também variáveis. Há inúmeras vantagens em coletar objetos em um package. Mais a frente serão vistos os benefícios de sua utilização.
Os packages geralmente possuem duas partes, uma que se refere a spec e a segunda parte que é o body. A spec é o que chamamos de interface para o package. É nela que declaramos os tipos, variáveis, constantes, exceções, cursores e subprogramas que poderão ser referenciados a partir de partes externas ao package. Por outro lado, no body definimos as consultas para os cursores e o código para os subprogramas. Em alguns casos pode ser que não haja a necessidade de se ter um body.
Em outras palavras, Package PL/SQL é um grupo de funções relacionadas, procedimentos, tipos, cursores, dentre outros. Os packages são como uma biblioteca, pois uma vez escrito é armazenado no banco de dados Oracle e pode ser usado por muitas aplicações.
De um certo modo, podemos imaginar a spec como sendo uma interface e o body como sendo uma caixa preta. Sabendo disso, podemos entender então que é possível depurar, aprimorar ou substituir bodies de um package sem precisar alterar as especificações do package. Para criar um spec de um package utilizamos a instrução SQL CREATE PACKAGE. E para a criação de um bod, utilizamos a instrução CREATE PACKAGE BODY.
A spec do package é a interface pública de suas aplicações. O público significa que a função armazenada, procedimentos, tipos, são acessíveis a partir de outras aplicações. Já o body contém o código que implementa as especificações do pacote.
O que podemos ter num package PL/SQL?
Vamos ver a seguir o que podemos definir em um pacote PL/SQL:
- Podemos ter métodos get e set para as variáveis do package.
- Declaração de cursores com o texto da consulta SQL. Reutilizar exatamente o mesmo texto de consulta em vários locais se torna mais rápido do que digitar novamente a mesma consulta, com ligeiras diferenças. Torna-se também mais fácil de manter, se precisarmos mudar uma consulta que é usada em muitos lugares.
- Declarações para exceções. Normalmente, precisamos ser capazes de referenciar estes a partir de diferentes procedures, de modo que podemos lidar com as exceções com chamadas de subprogramas.
- Declarações para procedures e functions que chamam um ao outro. Não precisamos nos preocupar com a ordem de compilação das procedures e functions “empacotadas”.
- Declarações para procedimentos e funções de sobrecarga (overload). Podemos criar várias variações de uma procedure ou function usando os mesmos nomes, mas com diferentes conjuntos de parâmetros.
- Podemos tratar variáveis num package como sendo variáveis globais.
- Tipos de declarações para coleções de tipos PL/SQL. Para passar uma coleção, como um parâmetro entre stored procedures ou functions, devemos declarar o tipo em um package. Deste modo, tanto a chamada quanto o chamado do subprograma podem ser referenciados.
Quais as vantagens de utilização de packages PL/SQL?
A utilização de packages tem uma longa história no que diz respeito a engenharia de softwares, oferecendo características importantes de fácil manutenção, reutilização de código, muitas vezes em esforços dentro de uma equipe de desenvolvimento para o desenvolvimento de grandes sistemas. Apresentaremos então alguns dos principais pontos vantajosos na utilização de packages.
- Modularidade - Packages permitem encapsular tipos relacionados logicamente, itens e sub-programas em um módulo PL/SQL. Cada package fica fácil de entender, e as interfaces entre eles tornam-se mais simples, claras e bem definidas. Isso ajuda e muito no desenvolver de aplicativos.
- Aplicação simples de design - Ao projetarmos um aplicativo, tudo que precisamos, inicialmente, é a informação de interface nas especificações da embalagem. Desta forma, podemos codificar e compilar uma especificação sem o seu corpo. Em seguida, os subprogramas armazenados que referenciam o pacote podem ser compilados também. De certa forma, não precisamos definir os corpos dos packgaes totalmente até que seja necessário.
- Ocultação de informações - Com os packages, podemos especificar quais tipos, itens e sub-programas serão públicos (visíveis e acessíveis) ou privados (escondido e inacessível). Por exemplo, se um pacote contém quatro subprogramas, três poderão ser públicos e um deles privado. O package esconde a implementação de subprogramas privados para que somente o próprio package (e não a sua aplicação) seja afetado se houver mudanças de implementação. Isso simplifica a manutenção e o aprimoramento no desenvolvimento. Além disso, ao esconder os detalhes da implementação dos usuários, isso remete a proteger a integridade dos packages.
- Melhor performance - Quando chamamos um subprograma empacotado pela primeira vez, todo o package é carregado na memória. O que quer dizer que quando se tem a necessidade de reutilização do package, ele será recuperado de maneira mais rápida e eficiente por estar em memória, não havendo a necessidade de busca no disco.
Os Packages param as dependências em cascata e evitam recompilações desnecessárias. Podemos pensar no seguinte exemplo, se alterarmos o body de uma function que está num package, o Oracle não irá recompilar outros subprogramas que chamam por esta function. Estes subprogramas só dependeram dos parâmetros e valores de retorno que serão declarados na especificação, então eles só são recompilados se houverem mudanças na spec e não quando as mudanças são no body.
Criando PL/SQL package specification
A especificação do package é necessário quando você cria um novo package. A especificação do package lista todos os objetos que são acessíveis ao público a partir de outras aplicações. A especificação do package também fornece as informações que os desenvolvedores precisam saber para usar a sua interface. Em suma, a especificação do package é a sua API.
Se a especificação do package não contém quaisquer funções armazenadas, procedures e nenhum código privado sendo necessário, então não precisamos ter um corpo para o package. Estes packages podem conter apenas as definições dos tipos e declaração de variáveis. Essas variáveis são conhecidas como dados do pacote. O escopo dos dados é global para as aplicações. Devido a isso é necessário encapsular tudo que não seja necessário como público e fazer uso das funções gets e sets para ler e escrever os dados. Ao fazer isso, podemos evitar que os dados dos package sejam alterados de forma errônea.
De acordo com a Listagem 1, seguem as instruções de como criar um package sem o body.
Listagem 1. Criação do package sem o body, apenas com as specs
CREATE [OR REPLACE] PACKAGE package_name
[ AUTHID { CURRENT_USER | DEFINER } ]
{ IS | AS }
[definitions of public TYPES
,declarations of public variables, types, and objects
,declarations of exceptions
,pragmas
,declarations of cursors, procedures, and functions
,headers of procedures and functions]
END [package_name];
A instrução CREATE PACKAGE é usada para definir uma nova especificação do pacote. Caso queiramos reconstruir o pacote existente, podemos usar a palavra-chave REPLACE em vez de CREATE. Na especificação do pacote podemos definir novos tipos, declarar variáveis globais, tipos, objetos, exceções, cursores, procedimentos e funções.
De acordo com a Listagem 2, temos um exemplo de criação de uma nova especificação de pacote chamado funcionário. O pacote de funcionário contém duas funções: get_nomeCompleto() e get_salario() com base na identificação do funcionário.
Listagem 2. Criação do package funcionário
CREATE OR REPLACE PACKAGE funcionario AS
// get nome completo do funcionario
FUNCTION get_nomeCompleto(n_func_id NUMBER)
RETURN VARCHAR2;
// get salario do funcionario
FUNCTION get_salario(n_func_id NUMBER)
RETURN NUMBER;
END funcionario;
Agora criaremos o corpo para o package, de acordo com a Listagem 3.
No body do package PL/SQL fica todo o código que implementa as funções armazenadas, procedures e cursores listados na especificação do package.
Listagem 3. Criação do body para o package funcionário
CREATE [OR REPLACE] PACKAGE BODY package_name
{ IS | AS }
[definitions of private TYPEs
,declarations of private variables, types, and objects
,full definitions of cursors
,full definitions of procedures and functions]
[BEGIN
sequence_of_statements
[EXCEPTION
exception_handlers ] ]
END [package_name];
A sintaxe é similar à da criação da spec, exceto pela palavra-chave BODY e o código implementado das especificações do package.
O corpo da package pode conter uma seção de inicialização do pacote opcional que aparece no final do corpo de package. As seções de inicialização das packages começam com a palavra-chave BEGIN e terminam com a seção do EXCEPTION ou a palavra-chave END que finaliza o pacote. A seção de inicialização do pacote é executada quando o aplicativo faz referência ao elemento do package pela primeira vez.
Agora que apresentamos o body do package, faremos um exemplo completo para o package funcionário iniciado anteriormente. Apresentamos o nosso exemplo na Listagem 4.
Listagem 4. Criação do exemplo completo de uma package com spec e body
/*
Package funcionario body
*/
CREATE OR REPLACE PACKAGE BODY funcionario AS
// get funcionário nomeCompleto
FUNCTION get_nomeCompleto(n_func_id NUMBER) RETURN VARCHAR2 IS
v_nomeCompleto VARCHAR2(46);
BEGIN
SELECT primeiro_nome || ',' || ultimo_nome
INTO v_nomeCompleto
FROM funcionarios
WHERE empresa_id = n_func_id;
RETURN v_nomeCompleto;
EXCEPTION
WHEN NO_DATA_FOUND THEN
RETURN NULL;
WHEN TOO_MANY_ROWS THEN
RETURN NULL;
END; // end get_nomeCompleto
get salario
FUNCTION get_salario(n_func_id NUMBER) RETURN NUMBER IS
n_salario NUMBER(8,2);
BEGIN
SELECT salario
INTO n_salario
FROM funcionarios
WHERE empresa_id = n_func_id;
RETURN n_salario;
EXCEPTION
WHEN NO_DATA_FOUND THEN
RETURN NULL;
WHEN TOO_MANY_ROWS THEN
RETURN NULL;
END;
END funcionario;
Ainda falta chamarmos nosso package, que será visto na Listagem 6. Para fazermos referência a nosso package utilizamos a seguinte notação mostrada na Listagem 5.
Listagem 5. Chamada do package
package_name.package_element
Listagem 6. chamando o package funcionário
SET SERVEROUTPUT ON SIZE 1000000;
DECLARE
n_salario NUMBER(8,2);
v_nome VARCHAR2(46);
n_func_id NUMBER := &empresa_id;
BEGIN
v_nome := funcionario.get_nomeCompleto(n_func_id);
n_salario := funcionario.get_salario(n_func_id);
IF v_nome IS NOT NULL AND
n_salario IS NOT NULL
THEN
dbms_output.put_line('Funcionário: ' || v_nome);
dbms_output.put_line('Recebe salário = ' || n_salario);
END IF;
END;