Работа с данными пакета (package) PL/SQL на примере

Работа с данными пакета (package) PL/SQL Данные пакета PL/SQL состоят из переменных и констант, определенных на уровне пакета, а не в конкретной его функции или процедуре. Их областью видимости является не отдель­ная программа, а весь пакет. Структуры данных пакета существуют (и сохраняют свои значения) на протяжении всего сеанса, а не только во время выполнения программы. Если данные пакета объявлены в его теле, они сохраняются в течение сеанса, но до­ступны только элементам пакета (то есть являются приватными).


Оглавление статьи[Показать]


Если данные пакета объявлены в его спецификации, они также сохраняются в те­чение всего сеанса, но их чтение и изменение разрешено любой пользовательской программе, обладающей привилегией EXECUTE для пакета. Общие данные пакета похожи на глобальные переменные Oracle Forms (а их использование сопряжено с таким же риском).

Если пакетная процедура открывает курсор, он остается открытым и доступным в ходе всего сеанса. Нет необходимости объявлять курсор в каждой программе. Один модуль может его открыть, а другой — выполнить выборку данных. Переменные пакета могут использоваться для передачи данных между транзакциями, поскольку они привязаны не к транзакции, а к сеансу.

 

Глобальные данные в сеансе Oracle

В среде PL/SQL структуры данных пакета функционируют как глобальные. Однако следует помнить, что они доступны только в пределах одного сеанса или подключения к базе данных Oracle и не могут совместно использоваться несколькими сеансами. Если доступ к данным нужно обеспечить для нескольких сеансов Oracle, используйте пакет DBMS_PIPE (его описание имеется в документации Oracle Built-In Packages).

Будьте осторожны с предположением о том, что разные части приложения всегда работают с Oracle через одно подключение. В некоторых случаях среда, из которой выполняется компонент приложения, устанавливает для него новое подключение. При этом данные пакета, записанные первым подключением, будут недоступны для второго.

Допустим, приложение Oracle Forms сохранило значение в пакетной структуре данных. Когда форма вызывает хранимую процедуру, эта процедура может обращаться к тем же пакетным переменным и значениям, что и форма, потому что они используют одно подключение к базе данных. Но допустим, форма генерирует отчет с использованием Oracle Reports. По умолчанию Oracle Reports создает для отчета отдельное подключе­ние к базе данных с тем же именем пользователя и паролем. Даже если отчет обратится к тому же пакету и структурам данных, что и форма, значения, хранимые в структурах данных, доступных форме и отчету, будут разными, поскольку сеанс отчета имеет свой экземпляр пакета и всех его структур.

По аналогии с двумя типами структур данных в пакетах (общедоступные и приватные) также существуют два типа глобальных данных пакетов: глобальные общедоступные данные и глобальные приватные данные. В следующих трех разделах статьи рассматриваются различные способы использования данных пакетов.

 

Глобальные общедоступные данные

Любая структура данных, объявленная в спецификации пакета, является глобальной общедоступной структурой данных; это означает, что к ней может обратиться любая

программа за пределами пакета. Например, вы можете определить коллекцию PL/ SQL в спецификации пакета и использовать ее для ведения списка работников, заслу­живших повышение. Вы также можете создать пакет с константами, которые должны использоваться во всех программах. Другие разработчики будут ссылаться на пакетные константы вместо использования фиксированных значений в программах. Глобальные общедоступные структуры данных также могут изменяться, если только они не были объявлены с ключевым словом CONSTANT.

Глобальные данные обычно считаются источником повышенной опасности в програм­мировании. Их очень удобно объявлять, они прекрасно подходят для того, чтобы вся информация была доступна в любой момент времени — однако зависимость от глобаль­ных структур данных приводит к созданию неструктурированного кода с множеством побочных эффектов.

Вспомните, что спецификация модуля должна сообщать полную информацию, не­обходимую для вызова и использования этого модуля. Однако по спецификации па­кета невозможно определить, выполняет ли пакет чтение и/или запись в глобальные структуры данных. По этой причине вы не можете быть уверены в том, что происходит в приложении и какая программа изменяет те или иные данные.

Передачу данных модулям и из них всегда рекомендуется осуществлять через параме­тры. В этом случае зависимость от структур данных документируется в спецификации и может учитываться разработчиками. С другой стороны, именованные глобальные структуры данных должны создаваться для информации, действительно глобальной по отношению к приложению — например, констант и параметров конфигурации.

Такие данные следует разместить в централизованном пакете. Однако учтите, что при такой архитектуре в приложении возникает «единая точка перекомпиляции»: каждый раз, когда вы вносите изменение в пакет и перекомпилируете спецификацию, многие программы приложения теряют работоспособность.

 

Пакетные курсоры

Одним из самых интересных типов пакетных данных является явный курсор PL/SQL. Его можно объявить в теле либо в спецификации пакета. Состояние курсора (открыт или закрыт), а также указатель на его набор данных сохраняются в течение всего сеанса. Это означает, что открыть пакетный курсор можно в одной программе, выбрать из него данные — в другой, а закрыть — в третьей. Такая гибкость курсоров предоставляет большие возможности, но в то же время она может стать источником проблем. Сначала мы рассмотрим некоторые тонкости объявления пакетных курсоров, а затем перейдем к открытию, выборке данных и закрытию таких курсоров.

 

Объявление пакетных курсоров

Явный курсор в спецификации пакета можно объявлять двумя способами:

  •  Полностью (заголовок курсора и запрос). Именно так объявляются курсоры в ло­кальных блоках PL/SQL.
  •  Частично (только заголовок курсора). В этом случае запрос определяется в теле пакета, поэтому реализация курсора скрыта от использующего пакет разработчика. При использовании второго способа в объявление нужно добавить секцию RETURN, ука­зывающую, какие данные будут возвращены при выборке из курсора. На самом деле эти данные определяются инструкцией SELECT, которая присутствует только в теле, но не в спецификации

 

В секции RETURN можно задать одну из следующих структур данных:

  • О запись, объявленная на основе таблицы базы данных с использованием атрибута %rowtype;
  • О запись, определенная программистом.

Объявление курсора в теле пакета осуществляется так же, как в локальном блоке PL/ SQL. Следующий пример спецификации пакета демонстрирует оба подхода:

PACKAGE book_info
IS
   CURSOR byauthor_cur (
      author_in IN books.author%TYPE
   )
   IS
      SELECT *
         FROM books
      WHERE author = author_in;

   CURSOR bytitle_cur (
      title_filter_in IN books.title%TYPE
   ) RETURN books%ROWTYPE;

   TYPE author_summary_rt IS RECORD (
      author books.author%TYPE,
      total_page_count PLS_INTEGER,
      total_book_count PLS_INTEGER);

   CURSOR summary_cur (
      author_in IN books.author%TYPE
   ) RETURN author_summary_rt;
END book_info;

Логика программы описана в следующей таблице.

 

Строки Описание
3-9 Типичное определение явного курсора, полностью заданное в спецификации пакета
11-13 Определение курсора без запроса. Спецификация указывает, что открыв курсор и выбрав
из него данные, пользователь получит одну строку из таблицы books под действием заданного фильтра
15-18 Определение нового типа записи для хранения информации об авторе
20-22 Объявление курсора, возвращающего сводную информацию о заданном авторе (всего три
значения)

 

Рассмотрим тело пакета и выясним, какой код необходимо написать для работы с каждым из этих курсоров:

PACKAGE BODY book_info
IS
   CURSOR bytitle_cur (
      title_filter_in IN books.title%TYPE
   ) RETURN books%ROWTYPE
   IS
      SELECT *
         FROM books
      WHERE title LIKE UPPER (title_filter_in);

   CURSOR summary_cur (
      author_in IN books.author%TYPE
   ) RETURN author_summary_rt
   IS
      SELECT author, SUM (page_count), COUNT (*)
         FROM books
      WHERE author = author_in;
END book_info;

Поскольку у нас имеются два курсора с секциями RETURN, их определения нужно за­вершить в теле пакета. Список выбираемых запросом элементов в теле пакета должен соответствовать (по количеству элементов и их типам данных) секции RETURN в специ­фикации пакета. Если это условие не соблюдается, то при компиляции пакета вы полу­чите одно из следующих сообщений об ошибке:

PLS-00323: subprogram or cursor '<cursor>' is declared in a 
package specification and must be defined in the package body 
PLS-00400: different number of columns between cursor SELECT 
statement and return value

 

Работа с пакетными курсорами

Теперь давайте посмотрим, как пользоваться пакетными курсорами. Прежде всего для открытия, выборки данных и закрытия вам не придется изучать новый синтаксис — нужно только задать имя пакета перед именем курсора. Например, чтобы запросить информацию о книгах по PL/SQL, можно выполнить такой блок кода:

DECLARE
   onebook book_info.bytitle_cur%ROWTYPE;
BEGIN
   OPEN book_info.bytitle_cur ('%PL/SQL%');
   LOOP
      EXIT WHEN book_info.bytitle_cur%NOTFOUND;
      FETCH book_info.bytitle_cur INTO onebook; 
      book_info.display (onebook);
   END LOOP;

   CLOSE book_info.bytitle_cur;
END;

Как видите, на основе пакетного курсора точно так же можно объявить переменную с использованием %ROWTYPE и проверить атрибуты. Ничего нового!

Однако и в этом простом фрагменте кода есть скрытый нюанс. Поскольку курсор объ­явлен в спецификации пакета, его область видимости не ограничивается конкретным блоком PL/SQL. Предположим, мы выполняем следующий код:

BEGIN -- Только открываем курсор...
   OPEN book_info.bytitle_cur ('%PEACE%');
END;

Если затем в том же сеансе выполнить приведенный анонимный блок в приведенном выше цикле LOOP, то Oracle выдаст сообщение об ошибке (ORA-06511).

Дело в том, что блок, выполненный первым, не закрыл курсор, и по завершении его работы курсор остался открытым.

При работе с пакетными курсорами необходимо всегда соблюдать следующие правила:

  •  Никогда не рассчитывайте на то, что курсор закрыт (и готов к открытию).
  •  Никогда не рассчитывайте на то, что курсор открыт (и готов к закрытию).
  •  Всегда явно закрывайте курсор после завершения работы с ним. Эту логику также необходимо включить в обработчики исключений; убедитесь в том, что курсор за­крывается на всех путях выхода из программы.

Если пренебречь этими правилами, то ваши приложения будут работать нестабильно, а в процессе их функционирования могут появиться неожиданные необработанные исклю­чения. Поэтому лучше написать процедуры, которые открывают и закрывают курсоры и учитывают все возможные их состояния. Этот подход реализован в следующем пакете:

PACKAGE personnel
IS
   CURSOR emps_for_dept (
      department_id_in_in IN employees.department_id%TYPE)
   IS
      SELECT * FROM employees 
      WHERE department_id = department_id_in;

   PROCEDURE open_emps_for_dept(
      department_id_in IN employees.department_id%TYPE, 
      close_if_open IN BOOLEAN := TRUE 
   );

   PROCEDURE close_emps_for_dept;
END personnel;

Как видите, вместе с курсором объявлены две сопутствующие процедуры для его от­крытия и закрытия. Если нам, скажем, потребуется перебрать в цикле строки курсора, это можно сделать так:

DECLARE
   one_emp personnel.emps_for_dept%ROWTYPE;
BEGIN
   personnel.open_emps_for_dept (1055);
   LOOP
      EXIT WHEN personnel.emps_for_dept%NOTFOUND;
      FETCH personnel.emps_for_dept INTO one_emp;
   END LOOP;
   personnel.close_emps_for_dept;
END;

В этом фрагменте не используются явные вызовы OPEN и CLOSE. Вместо них вызываются соответствующие процедуры, скрывающие особенности работы с пакетными курсорами.

Можно  взглянуть на ситуацию под другим углом:

...Вместо того чтобы работать с пакетными курсорами, можно добиться точно такого же эффекта посредством инкапсуляции логики и данных в представлениях и опубликовать их для разработчиков. В этом случае разработчики будут нести ответственность за сопровождение своих курсоров; дело в том, что обеспечить нор­мальное сопровождение с инструментарием, существующим для общедоступных пакетных курсоров, невозможно. А именно, насколько мне известно, невозможно гарантировать использование процедур открытия/закрытия, но курсор всегда будет оставаться видимым для разработчика, который открывает/закрывает его напря­мую; следовательно, такая конструкция остается уязвимой. Проблема усугубляется тем, что использование общедоступных пакетных курсоров и процедур открытия/ закрытия может породить в группе ложное чувство безопасности и защищенности».

Пакетное создание курсоров и предоставление доступа к ним всем разработчикам, участвующим в проекте, приносит большую пользу. Проектирование оптимальных структур данных приложения — непростая и кропотливая работа. Эти структуры — и хра­нящиеся в них данные — используются в программах PL/SQL, а работа с ними почти всегда осуществляется через курсоры. Если вы не определите свои курсоры в пакетах и не предоставите их «в готовом виде» всем разработчикам, то каждый будет писать собственную реализацию курсора, а это создаст массу проблем с производительностью и сопровождением кода. Пакетные курсоры являются лишь одним из примеров инкап­суляции доступа к структурам данных.

 

Повторно инициализируемые пакеты

По умолчанию пакетные данные сохраняются в течение всего сеанса (или до переком­пиляции пакета). Это исключительно удобное свойство пакетов, но и у него имеются определенные недостатки:

  •  Постоянство глобально доступных (общих и приватных) структур данных сопро­вождается нежелательными побочными эффектами. В частности, можно случайно оставить пакетный курсор открытым, а в другой программе попытаться открыть его без предварительной проверки, что приведет к ошибке.
  •  Если данные хранятся в структурах уровня пакетов, то программа может занять слишком большой объем памяти, не освобождая ее.

Для оптимизации использования памяти при работе с пакетами можно использовать директиву SERIALLY_REUSABLE. Она указывает Oracle, что пакет является повторно инициализируемым, то есть его состояние (состояние переменных, открытых пакетных курсоров и т. п.) нужно сохранять не на протяжении сеанса, а на время одного вызова пакетной программы.

Рассмотрим действие этой директивы на примере пакета book_info. В нем имеются две отдельные программы: для заполнения списка книг и для вывода этого списка.

PACKAGE book_info 
IS
   PRAGMA SERIALLY_REUSABLE;
   PROCEDURE fill_list;
   PROCEDURE show_list;
END;

Как видно из приведенного ниже тела пакета, список объявляется как приватный гло­бальный ассоциативный массив:

PACKAGE BODY book_info 
IS
   PRAGMA SERIALLY_REUSABLE;

   TYPE book_list_t 
   IS
      TABLE OF books%ROWTYPE 
      INDEX BY PLS_INTEGER; 
      my_books book_list_t;

   PROCEDURE fill_list 
   IS
   BEGIN
      FOR rec IN (SELECT *
         FROM books
         WHERE author LIKE '%FEUERSTEIN%')
      LOOP
         my_books (my_books.COUNT + 1) := rec;
      END LOOP;
   END fill_list;

   PROCEDURE show_list	
   IS
   BEGIN
      IF my_books.COUNT = 0 
      THEN
         DBMS_OUTPUT.PUT_LINE ('** Книг нет...');
      ELSE
         FOR indx IN 1 .. my_books.COUNT 
         LOOP
            DBMS_OUTPUT.PUT_LINE (my_books (indx).title);
         END LOOP;
      END IF;
   END show_list;
END;


Чтобы увидеть, как работает эта директива, заполним список и выведем его на экран. В первом варианте оба шага выполняются в одном блоке:

SQL> BEGIN
2       DBMS_OUTPUT.PUT_LINE (
3         'Заполнение и вывод в одном блоке:'
4       );
5       book_info.fill_list;
6       book_info.show_list;
7    END;
8 /

Заполнение и вывод в одном блоке:

Oracle PL/SQL Programming
Oracle PL/SQL Best Practices
Oracle PL/SQL Built-in Packages

Во второй версии заполнение и вывод списка производятся в разных блоках. В резуль­тате коллекция окажется пустой:

SQL> BEGIN
2       DBMS_OUTPUT.PUT_LINE ('Заполнение в первом блоке');
3       book_info.fill_list;
4    END;
5 /

Заполнение в первом блоке

SQL> BEGIN
2       DBMS_OUTPUT.PUT_LINE ('Вывод во втором блоке:');
3       book_info.show_list;
4    END;
5 /

Вывод во втором блоке:

** Нет книг...

Работая с повторно инициализируемыми пакетами, необходимо учитывать некоторые особенности:

  •  Глобальная память для такого пакета выделяется в системной глобальной области (SGA) базы данных Oracle, а не в пользовательской глобальной области (UGA), что позволяет повтор­но применять рабочую область пакета. При каждом новом обращении к пакету его общие переменные инициализируются значениями по умолчанию или значением NULL, а его инициализационный раздел выполняется повторно.
  •  Максимальное количество рабочих областей, необходимых для повторно инициа­лизируемого пакета, равно количеству одновременно работающих с этим пакетом пользователей. Увеличение объема используемой памяти в SGA компенсируется уменьшением объема памяти, задействованного в UGA. Когда нужно предоставить память из SGA другим запросам, Oracle освобождает неиспользуемые области памяти.

 

Вас заинтересует / Intresting for you:

Встроенные методы коллекций PL...
Встроенные методы коллекций PL... 14849 просмотров sepia Tue, 29 Oct 2019, 09:54:01
Управление приложениями PL/SQL...
Управление приложениями PL/SQL... 4651 просмотров Stas Belkov Thu, 16 Jul 2020, 06:20:48
Символьные функции и аргументы...
Символьные функции и аргументы... 18590 просмотров Анатолий Wed, 23 May 2018, 18:54:01
Тип данных RAW в PL/SQL
Тип данных RAW в PL/SQL 12352 просмотров Doctor Thu, 12 Jul 2018, 08:41:33
Войдите чтобы комментировать