Neste material, você aprenderá sobre:
- Criação e Estrutura de Pacotes
- Instanciação e Inicialização de Pacotes
- Pacotes SERIALLY_REUSABLE
Os pacotes PL/SQL são uma poderosa ferramenta de organização e modularização de código no Oracle Database. Eles permitem agrupar procedimentos, funções, variáveis e outros objetos relacionados, proporcionando melhor gerenciamento de código e encapsulamento.
Lab 15.1: Criando Pacotes PL/SQL
Após este laboratório, você será capaz de:
- Criar uma Especificação de Pacote
- Implementar um Corpo de Pacote
Imagine um pacote PL/SQL como uma caixa de ferramentas bem organizada. Assim como uma caixa de ferramentas tem compartimentos para diferentes tipos de ferramentas, um pacote PL/SQL agrupa objetos relacionados, como procedimentos, funções e variáveis, em uma única unidade lógica.
Criando uma Especificação de Pacote
A especificação de pacote é como o rótulo na parte externa da caixa de ferramentas. Ela lista todas as ferramentas (ou, no nosso caso, objetos PL/SQL) que estão disponíveis para uso, sem mostrar os detalhes internos de como essas ferramentas funcionam.
Pense na especificação do pacote como um contrato entre o desenvolvedor do pacote e os usuários do pacote. Ela define claramente o que o pacote oferece, sem revelar como ele faz isso internamente.
Listing 15.1 Criando uma Especificação de Pacote
1 2 3 |
CREATE [OR REPLACE] PACKAGE nome_pacote AS -- Declarações públicas END nome_pacote; |
Vamos criar um pacote para gerenciar funcionários:
Por exemplo emp_mgmt_pkg_spec.sql:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
CREATE OR REPLACE PACKAGE emp_mgmt_pkg AS -- Tipo de registro personalizado TYPE emp_record_type IS RECORD ( employee_id employees.employee_id%TYPE, first_name employees.first_name%TYPE, last_name employees.last_name%TYPE, email employees.email%TYPE, hire_date employees.hire_date%TYPE, job_id employees.job_id%TYPE, salary employees.salary%TYPE ); -- Tipo de tabela de registros TYPE emp_table_type IS TABLE OF emp_record_type INDEX BY PLS_INTEGER; -- Funções e procedimentos públicos FUNCTION get_employee(p_emp_id IN employees.employee_id%TYPE) RETURN emp_record_type; PROCEDURE update_salary(p_emp_id IN employees.employee_id%TYPE, p_new_salary IN employees.salary%TYPE); FUNCTION get_department_employees(p_dept_id IN departments.department_id%TYPE) RETURN emp_table_type; END emp_mgmt_pkg; |
Neste exemplo, estamos criando uma “caixa de ferramentas” chamada emp_mgmt_pkg
para gerenciar funcionários. Observe como declaramos tipos personalizados (emp_record_type
e emp_table_type
) e três subprogramas públicos.
Explicação Detalhada:
- Tipos Personalizados:
emp_record_type
: É como criar um molde personalizado para armazenar informações de um funcionário. Imagine-o como um formulário com campos específicos para cada detalhe do funcionário.emp_table_type
: Este é como um arquivo de fichas, onde cada ficha é umemp_record_type
. Ele nos permite trabalhar com múltiplos funcionários de uma vez.
- Subprogramas Públicos:
get_employee
: Esta função é como um localizador de funcionários. Você fornece um ID e ela retorna todas as informações daquele funcionário.update_salary
: Este procedimento é como um formulário de atualização de salário. Você informa o ID do funcionário e o novo salário, e ele faz a atualização.get_department_employees
: Esta função é como um relatório de departamento. Você informa o ID do departamento e ela retorna informações de todos os funcionários daquele departamento.
Implementando o Corpo do Pacote
O corpo do pacote é onde a mágica acontece. Se a especificação é o rótulo da caixa de ferramentas, o corpo é o interior da caixa, onde as ferramentas realmente estão e como elas funcionam.
Listing 15.2 Criando o Corpo do Pacote
1 2 3 |
CREATE [OR REPLACE] PACKAGE BODY nome_pacote AS -- Implementações e objetos privados END nome_pacote; |
Vamos implementar o corpo do nosso pacote:
Exemplo emp_mgmt_pkg_body.sql:
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 |
CREATE OR REPLACE PACKAGE BODY emp_mgmt_pkg AS -- Cursor privado CURSOR emp_cur(p_dept_id departments.department_id%TYPE) IS SELECT employee_id, first_name, last_name, email, hire_date, job_id, salary FROM employees WHERE department_id = p_dept_id; -- Implementação da função get_employee FUNCTION get_employee(p_emp_id IN employees.employee_id%TYPE) RETURN emp_record_type IS v_emp emp_record_type; BEGIN SELECT employee_id, first_name, last_name, email, hire_date, job_id, salary INTO v_emp FROM employees WHERE employee_id = p_emp_id; RETURN v_emp; EXCEPTION WHEN NO_DATA_FOUND THEN RAISE_APPLICATION_ERROR(-20001, 'Funcionário não encontrado.'); END get_employee; -- Implementação do procedimento update_salary PROCEDURE update_salary(p_emp_id IN employees.employee_id%TYPE, p_new_salary IN employees.salary%TYPE) IS BEGIN UPDATE employees SET salary = p_new_salary WHERE employee_id = p_emp_id; IF SQL%ROWCOUNT = 0 THEN RAISE_APPLICATION_ERROR(-20002, 'Funcionário não encontrado para atualização.'); END IF; COMMIT; END update_salary; -- Implementação da função get_department_employees FUNCTION get_department_employees(p_dept_id IN departments.department_id%TYPE) RETURN emp_table_type IS v_emp_table emp_table_type; v_index PLS_INTEGER := 1; BEGIN FOR emp_rec IN emp_cur(p_dept_id) LOOP v_emp_table(v_index) := emp_rec; v_index := v_index + 1; END LOOP; RETURN v_emp_table; END get_department_employees; END emp_mgmt_pkg; |
Explicação Detalhada:
- Cursor Privado:
emp_cur
: Este cursor é como um scanner interno que lê informações de funcionários de um departamento específico. É privado, o que significa que só pode ser usado dentro do pacote.
- Implementação de get_employee:
- Esta função é como um robô de busca. Ele vai ao “arquivo” (tabela employees), procura a “ficha” do funcionário com o ID fornecido e retorna todas as informações.
- Se não encontrar o funcionário, ele “grita” (lança um erro) dizendo que o funcionário não foi encontrado.
- Implementação de update_salary:
- Este procedimento é como um formulário de atualização automática. Ele preenche o novo salário na “ficha” do funcionário.
- Se não conseguir encontrar a “ficha” para atualizar, ele também “grita” um erro.
- O
COMMIT
no final é como carimbar “APROVADO” no formulário, tornando a mudança permanente.
- Implementação de get_department_employees:
- Esta função é como um assistente que pega todas as “fichas” de funcionários de um departamento e as organiza em uma pasta.
- Ele usa o cursor
emp_cur
para “escanear” cada ficha e a coloca na “pasta” (v_emp_table
).
Analogia Final
Pense no pacote como um departamento de RH bem organizado:
- A especificação do pacote é como o balcão de atendimento, onde você vê quais serviços estão disponíveis.
- O corpo do pacote é o escritório nos fundos, onde o trabalho real acontece.
- As funções e procedimentos são como os diferentes serviços oferecidos pelo RH (buscar informações de funcionários, atualizar salários, listar funcionários de um departamento).
- Os tipos personalizados são como os formulários padronizados que o RH usa para manter tudo organizado.
Ao usar pacotes, você está essencialmente criando um “mini-aplicativo” dentro do banco de dados, com uma interface clara (especificação) e uma implementação bem organizada (corpo), facilitando a manutenção e o uso do código.
Lab 15.2: Instanciação e Estado do Pacote
Após este laboratório, você entenderá:
- Como os pacotes são instanciados
- O conceito de estado do pacote
Instanciação de Pacotes
Imagine um pacote PL/SQL como um robô assistente sofisticado. Quando você liga esse robô pela primeira vez em uma sessão de banco de dados, ele passa por um processo de “inicialização” – isso é o que chamamos de instanciação do pacote.
O Processo de Instanciação
- Carregamento: O Oracle carrega o pacote na memória.
- Inicialização de Variáveis: Todas as variáveis públicas do pacote são inicializadas com seus valores padrão ou especificados.
- Execução do Código de Inicialização: Se houver algum código de inicialização no corpo do pacote (fora de qualquer procedimento ou função), ele é executado.
Analogia: Pense nisso como ligar seu smartphone pela manhã. Ele carrega o sistema operacional (carregamento do pacote), configura suas configurações padrão (inicialização de variáveis) e verifica notificações ou atualizações pendentes (execução do código de inicialização).
Estado do Pacote
O estado do pacote refere-se aos valores atuais de suas variáveis e ao status de seus cursores em um determinado momento. É como a “memória” do nosso robô assistente.
Características do Estado do Pacote:
- Persistência na Sessão: O estado persiste durante toda a sessão do usuário.
- Compartilhamento: Todos os usuários da sessão compartilham o mesmo estado do pacote.
- Reinicialização: O estado é reiniciado quando a sessão termina ou quando o pacote é recompilado.
Analogia: Imagine o estado do pacote como o bloco de notas do nosso robô assistente. Ele mantém anotações (valores de variáveis) durante todo o dia de trabalho (sessão), e todos que interagem com o robô veem as mesmas anotações. No final do dia (fim da sessão), o bloco de notas é limpo para o próximo dia.
Vamos ver um exemplo prático:
Por exemplo pkg_state_demo.sql:
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 |
CREATE OR REPLACE PACKAGE state_demo_pkg AS v_count NUMBER := 0; PROCEDURE increment_count; FUNCTION get_count RETURN NUMBER; END state_demo_pkg; / CREATE OR REPLACE PACKAGE BODY state_demo_pkg AS PROCEDURE increment_count IS BEGIN v_count := v_count + 1; END increment_count; FUNCTION get_count RETURN NUMBER IS BEGIN RETURN v_count; END get_count; BEGIN -- Código de inicialização DBMS_OUTPUT.PUT_LINE('Pacote state_demo_pkg inicializado.'); END state_demo_pkg; / -- Teste do estado do pacote BEGIN DBMS_OUTPUT.PUT_LINE('Contagem inicial: ' || state_demo_pkg.get_count); state_demo_pkg.increment_count; state_demo_pkg.increment_count; DBMS_OUTPUT.PUT_LINE('Contagem após incrementos: ' || state_demo_pkg.get_count); END; / |
Explicação Detalhada:
- Variável de Pacote (v_count):
- É como um contador que nosso robô assistente mantém.
- Inicializada com 0 quando o pacote é instanciado.
- Procedimento increment_count:
- É como um botão no robô que, quando pressionado, aumenta o contador em 1.
- Função get_count:
- É como pedir ao robô para mostrar o valor atual do contador.
- Código de Inicialização:
- A mensagem “Pacote state_demo_pkg inicializado.” é exibida quando o pacote é instanciado pela primeira vez.
- Bloco de Teste:
- Mostra como o estado do pacote (o valor de v_count) muda e persiste entre chamadas de procedimentos.
Implicações Práticas do Estado do Pacote
- Compartilhamento de Dados: Útil para manter informações que precisam ser compartilhadas entre diferentes partes de uma aplicação.
- Cuidado com Concorrência: Se múltiplos usuários estão alterando o estado do pacote simultaneamente, pode haver problemas de concorrência.
- Desempenho: Manter estado pode melhorar o desempenho, evitando a necessidade de recuperar informações repetidamente.
- Limpeza: É importante considerar a limpeza ou reinicialização do estado quando necessário, especialmente em aplicações de longa duração.
Analogia Final
Pense no estado do pacote como um quadro branco em uma sala de reuniões:
- O quadro é limpo no início do dia (instanciação do pacote).
- Durante o dia, várias pessoas podem escrever e ler informações do quadro (manipulação do estado do pacote).
- Todos na sala podem ver o que está escrito (compartilhamento do estado).
- No final do dia, o quadro é limpo novamente (fim da sessão ou recompilação do pacote).
Entender o estado do pacote é crucial para desenvolver aplicações PL/SQL eficientes e confiáveis, permitindo que você aproveite ao máximo o compartilhamento de dados e a persistência de informações durante uma sessão de banco de dados.
Lab 15.3: Pacotes SERIALLY_REUSABLE
Após este laboratório, você saberá:
- Como criar e usar pacotes SERIALLY_REUSABLE
- As diferenças entre pacotes regulares e SERIALLY_REUSABLE
Conceito de Pacotes SERIALLY_REUSABLE
Pacotes SERIALLY_REUSABLE são uma variação especial dos pacotes PL/SQL padrão, projetados para otimizar o uso de memória em cenários específicos.
Analogia: Imagine um restaurante fast-food muito movimentado. Em um restaurante normal (pacote padrão), cada cliente teria sua própria mesa reservada durante toda a refeição, mesmo depois de terminar de comer. Em um restaurante SERIALLY_REUSABLE, assim que um cliente termina e sai, a mesa é imediatamente limpa e disponibilizada para o próximo cliente.
Características dos Pacotes SERIALLY_REUSABLE
- Liberação de Memória: Após cada “chamada” ao pacote, a memória é liberada.
- Reinicialização: O pacote é reinicializado a cada nova chamada.
- Não Persistência: O estado não persiste entre chamadas.
Criando um Pacote SERIALLY_REUSABLE
Para criar um pacote SERIALLY_REUSABLE, usamos a pragma SERIALLY_REUSABLE tanto na especificação quanto no corpo do pacote.
Por examplo serially_reusable_demo.sql:
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 |
CREATE OR REPLACE PACKAGE sr_demo_pkg AS PRAGMA SERIALLY_REUSABLE; v_last_employee employees.employee_id%TYPE; PROCEDURE set_last_employee(p_emp_id employees.employee_id%TYPE); FUNCTION get_last_employee RETURN employees.employee_id%TYPE; END sr_demo_pkg; / CREATE OR REPLACE PACKAGE BODY sr_demo_pkg AS PRAGMA SERIALLY_REUSABLE; PROCEDURE set_last_employee(p_emp_id employees.employee_id%TYPE) IS BEGIN v_last_employee := p_emp_id; END set_last_employee; FUNCTION get_last_employee RETURN employees.employee_id%TYPE IS BEGIN RETURN v_last_employee; END get_last_employee; END sr_demo_pkg; / -- Teste do pacote SERIALLY_REUSABLE BEGIN sr_demo_pkg.set_last_employee(100); DBMS_OUTPUT.PUT_LINE('Último funcionário: ' || sr_demo_pkg.get_last_employee); END; / BEGIN -- O valor não persiste entre chamadas DBMS_OUTPUT.PUT_LINE('Último funcionário (nova chamada): ' || sr_demo_pkg.get_last_employee); END; / |
Explicação Detalhada
- PRAGMA SERIALLY_REUSABLE:
- Esta diretiva informa ao Oracle que o pacote deve ser tratado como serialmente reutilizável.
- É como colocar um aviso de “Limpar Mesa Após Uso” em cada mesa do nosso restaurante fast-food.
- v_last_employee:
- Esta variável armazena o ID do último funcionário processado.
- Em um pacote normal, este valor persistiria entre chamadas. Aqui, ele é “esquecido” após cada uso.
- set_last_employee e get_last_employee:
- Estes são como o garçom que anota o pedido (set) e traz a comida (get).
- Em um pacote SERIALLY_REUSABLE, é como se o garçom “esquecesse” o pedido anterior assim que o entrega.
- Bloco de Teste:
- O primeiro bloco define um valor e o recupera imediatamente – isso funciona como esperado.
- O segundo bloco tenta recuperar o valor definido anteriormente, mas ele não existe mais.
Quando Usar Pacotes SERIALLY_REUSABLE
- Alto Volume de Transações: Ideal para aplicações com muitas transações curtas.
- Memória Limitada: Útil quando a memória do servidor é uma preocupação.
- Sem Necessidade de Estado Persistente: Quando você não precisa manter informações entre chamadas.
Analogia Expandida
Imagine uma linha de produção em uma fábrica:
- Pacote Padrão: Cada trabalhador tem sua própria estação de trabalho permanente com todas as ferramentas.
- Pacote SERIALLY_REUSABLE: Os trabalhadores compartilham estações de trabalho. Cada um usa a estação, completa sua tarefa, limpa tudo e passa para o próximo.
Considerações Importantes
- Desempenho: Pode ser mais lento para operações frequentes devido à reinicialização constante.
- Debugging: Pode ser mais difícil de debugar devido à natureza transitória do estado.
- Limitações: Não pode ser usado em triggers ou referenciado diretamente em SQL.
Atenção!
Pacotes SERIALLY_REUSABLE não são adequados para todas as situações. Use-os com cautela e apenas quando os benefícios de economia de memória superarem a necessidade de manter o estado entre chamadas.
Exercício Prático
Tente modificar o exemplo dado para incluir um contador que rastreie quantas vezes o pacote foi chamado. Observe como o contador se comporta diferentemente em um pacote SERIALLY_REUSABLE comparado a um pacote padrão.
Compreender os pacotes SERIALLY_REUSABLE adiciona uma ferramenta poderosa ao seu arsenal de desenvolvimento PL/SQL, permitindo otimizações de memória em cenários específicos de alto volume e baixa persistência de estado.
Atenção!
Pacotes SERIALLY_REUSABLE não podem ser referenciados em triggers ou diretamente em instruções SQL.
Comparação Detalhada: Pacotes Padrão vs. SERIALLY_REUSABLE
Para entender melhor as diferenças, vamos criar dois pacotes similares – um padrão e um SERIALLY_REUSABLE – e compará-los:
Por exemplo package_comparison.sql:
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 61 62 63 64 65 66 67 68 |
-- Pacote Padrão CREATE OR REPLACE PACKAGE std_pkg AS v_count NUMBER := 0; PROCEDURE increment_count; FUNCTION get_count RETURN NUMBER; END std_pkg; / CREATE OR REPLACE PACKAGE BODY std_pkg AS PROCEDURE increment_count IS BEGIN v_count := v_count + 1; END increment_count; FUNCTION get_count RETURN NUMBER IS BEGIN RETURN v_count; END get_count; END std_pkg; / -- Pacote SERIALLY_REUSABLE CREATE OR REPLACE PACKAGE sr_pkg AS PRAGMA SERIALLY_REUSABLE; v_count NUMBER := 0; PROCEDURE increment_count; FUNCTION get_count RETURN NUMBER; END sr_pkg; / CREATE OR REPLACE PACKAGE BODY sr_pkg AS PRAGMA SERIALLY_REUSABLE; PROCEDURE increment_count IS BEGIN v_count := v_count + 1; END increment_count; FUNCTION get_count RETURN NUMBER IS BEGIN RETURN v_count; END get_count; END sr_pkg; / -- Teste dos pacotes BEGIN -- Teste do pacote padrão DBMS_OUTPUT.PUT_LINE('Pacote Padrão:'); std_pkg.increment_count; std_pkg.increment_count; DBMS_OUTPUT.PUT_LINE('Contagem: ' || std_pkg.get_count); -- Nova chamada DBMS_OUTPUT.PUT_LINE('Nova chamada - Pacote Padrão:'); DBMS_OUTPUT.PUT_LINE('Contagem: ' || std_pkg.get_count); -- Teste do pacote SERIALLY_REUSABLE DBMS_OUTPUT.PUT_LINE('Pacote SERIALLY_REUSABLE:'); sr_pkg.increment_count; sr_pkg.increment_count; DBMS_OUTPUT.PUT_LINE('Contagem: ' || sr_pkg.get_count); -- Nova chamada DBMS_OUTPUT.PUT_LINE('Nova chamada - Pacote SERIALLY_REUSABLE:'); DBMS_OUTPUT.PUT_LINE('Contagem: ' || sr_pkg.get_count); END; / |
Análise do Resultado
Quando você executa este script, verá algo como:
1 2 3 4 5 6 7 8 9 |
Pacote Padrão: Contagem: 2 Nova chamada - Pacote Padrão: Contagem: 2 Pacote SERIALLY_REUSABLE: Contagem: 2 Nova chamada - Pacote SERIALLY_REUSABLE: Contagem: 0 |
Explicação
- Pacote Padrão:
- O contador mantém seu valor (2) entre as chamadas.
- É como um contador manual que você deixa na sua mesa; ele mantém o valor até que você o resete explicitamente.
- Pacote SERIALLY_REUSABLE:
- O contador é reiniciado para 0 na nova chamada.
- É como um contador digital que volta a zero automaticamente após cada uso.
Cenários de Uso
- Pacotes Padrão:
- Ideal para manter estado entre chamadas.
- Útil para caching de dados, contadores persistentes, ou manter informações de sessão.
- Exemplo: Um pacote que mantém preferências do usuário durante uma sessão de aplicação web.
- Pacotes SERIALLY_REUSABLE:
- Ideal para operações isoladas que não dependem de estado anterior.
- Útil em ambientes com muitos usuários concorrentes onde a memória é uma preocupação.
- Exemplo: Um pacote que processa transações bancárias individuais, onde cada transação é independente.
Considerações de Desempenho
- Overhead de Inicialização:
- Pacotes SERIALLY_REUSABLE têm um pequeno overhead de reinicialização a cada chamada.
- Em operações muito frequentes, isso pode impactar o desempenho.
- Uso de Memória:
- Pacotes padrão consomem mais memória, especialmente com muitas sessões concorrentes.
- Pacotes SERIALLY_REUSABLE são mais eficientes em termos de memória em larga escala.
Exercício Avançado
Tente criar um pacote SERIALLY_REUSABLE que simule um gerador de números de pedido. O pacote deve:
- Gerar um número de pedido único a cada chamada.
- Resetar o contador para 1 no início de cada dia.
- Usar a data atual como parte do número do pedido.
Dica: Você precisará combinar o uso de variáveis de pacote com funções de data do Oracle para alcançar este comportamento.
Conclusão
Pacotes SERIALLY_REUSABLE são uma ferramenta poderosa no arsenal do desenvolvedor PL/SQL, oferecendo uma maneira de otimizar o uso de memória em cenários específicos. No entanto, eles não são uma solução universal e devem ser usados com compreensão clara de suas implicações. A escolha entre pacotes padrão e SERIALLY_REUSABLE depende das necessidades específicas de sua aplicação, considerando fatores como persistência de estado, concorrência de usuários e requisitos de memória.