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;