Пакеты в PL/SQL: предназначение и применение на практике

ПАКЕТЫ PL/SQL - ПРИМЕНЕНИЕПакет PL/SQL представляет собой сгруппированный по определенным правилам именованный набор элементов кода PL/SQL. Он обеспечивает логическую структуру для организации программ и других элементов PL/SQL: курсоров, типов данных и переменных. Пакеты обладают очень важными функциональными возможностями, включая возможность сокрытия логики и данных, а также определения глобальных данных, существующих в течение сеанса.


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


 

Для чего нужны пакеты в PL/SQL?

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

  •  Упрощение сопровождения и расширения приложений. По мере того как все большая часть кодовой базы перемещается в режим сопровождения, качество при­ложений PL/SQL определяется не только их производительностью, но и простотой сопровождения. С этой точки зрения пакеты играют исключительно важную роль, поскольку они обеспечивают инкапсуляцию кода (в частности, они позволяют скрыть команды SQL за интерфейсом процедур), дают возможность определять кон­станты для литералов и «волшебных» чисел, и группировать логически связанные функции. Пакетный подход к проектированию и реализации сокращает количество потенциальных сбоев в приложениях.
  •  Повышение производительности приложений. Во многих ситуациях использова­ние пакетов повышает производительность и эффективность работы приложений. Определение постоянных структур данных уровня пакета позволяет кэшировать статические значения из базы данных. Это дает возможность избежать повторных запросов, а следовательно, значительно ускорить получение результата. Кроме того, подсистема управления памятью Oracle оптимизирована для доступа к откомпили­рованному коду пакетов.
  •  Исправление недостатков приложений или встроенных элементов. Некоторые из существующих программных компонентов Oracle имеют недостатки; в частности, не лучшим образом реализованы важнейшие функции встроенных пакетов UTL_FILE и DBMS_OUTPUT. Мириться с ними не обязательно; можно разработать собственный пакет на базе существующего, исправив как можно больше проблем. Например, сценарий do.pkg, предоставляет замену для встроенной функции DBMS_OUTPUT.PUT_LINE с добавлением перегрузки для типа XMLType. Подобного результата можно достичь и с помощью отдельных функций и процедур PL/SQL, но решение с пакетами более предпочтительно.
  •  Снижение необходимости в перекомпиляции кода. Пакет обычно состоит из двух элементов: спецификации и тела. Внешние программы (не определенные в пакете) могут вызывать только программы, перечисленные в спецификации. Изменение и перекомпиляция тела пакета не отражается на работе этих внешних программ. Снижение необходимости в перекомпиляции кода является важнейшим фактором администрирования больших объемов программного кода приложений. Концепция пакетов очень проста. Единственная сложность заключается в том, чтобы научиться эффективно применять в приложениях их богатые возможности. В этой статье мы начнем с рассмотрения простого пакета; вы увидите, что основные преимущества пакетов проявляются даже в тривиальном коде. Затем будет рассмотрен специальный синтаксис, используемый при определении пакетов.

Прежде чем приступать к рассмотрению преимуществ пакетов и описанию синтак­сиса их определения, необходимо сделать одно важное замечание. Всегда стройте приложение на основе пакетов; избегайте отдельных процедур и функций. Даже если вам сейчас кажется, что для реализации определенной возможности доста­точно одной процедуры или функции, в будущем к ней почти наверняка добавятся еще несколько. Когда вы поймете, что их лучше объединить в пакет, придется ис­кать все вызовы процедур и функций и добавлять к ним префикс с именем пакета. Используйте пакеты с самого начала, избавьте себя от будущих проблем!

 

Демонстрация возможностей пакетов PL/SQL

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

PROCEDURE process_employee (
   employee_id_in IN employees.employee_id%TYPE)
IS
   l_fullname VARCHAR2(100);
BEGIN
   SELECT last_name || ',' || first_name 
   INTO l_fullname 
   FROM employees
   WHERE employee_id = employee_id_in;
END;

Однако этот вроде бы тривиальный код обладает рядом скрытых недостатков:

  •  Длина переменной l_fullname жестко закодирована. Поскольку полное имя — про­изводное значение, которое строится конкатенацией содержимого двух столбцов, лучше так не делать. Если длина столбцов last_name и/или first_name будет увели­чена, код процедуры придется изменять.
  •  Жестко закодировано правило составления полного имени. Чем это плохо? Тем, что если через какое-то время пользователь захочет получить полное имя в формате «ИМЯ ФАМИЛИЯ», вам придется производить замену во многих местах кода.
  •  Наконец, этот очень распространенный запрос может встречаться в нескольких ме­стах приложения. Дублирование кода SQL затрудняет сопровождение приложения и его оптимизацию.

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

PACKAGE employee_pkg
AS
   SUBTYPE fullname_t IS	VARCHAR2 (200);

   FUNCTION fullname (
      last_in employees.last_name%TYPE,
      first_in employees.first_name%TYPE)
      RETURN fullname_t;

   FUNCTION fullname (
      employee_id_in IN employees.employee_id%TYPE)
      RETURN fullname_t;
END employee_pkg;

Фактически здесь перечисляются различные элементы, которые должны использоваться разработчиками. Важнейшие элементы кода представлены в следующей таблице.

Строки Описание
3 Объявление нового типа данных fullname_t. В этой версии его максимальная длина составляет 200 символов, но впоследствии ее будет легко изменить
5-8 Объявление функции fullname, которая строит полное имя по фамилии и имени. Обратите
внимание: способ построения полного имени в спецификации пакета не указан
10-13 Объявление второй функции с тем же именем fullname; новая версия получает первичный
ключ таблицы и возвращает соответствующее ему полное имя. Это типичный пример перегрузки, о которой говорилось в этой статье


Но прежде чем рассматривать реализацию пакета, давайте перепишем исходный блок кода таким образом, чтобы в нем использовались элементы пакета (обратите внимание на точечный синтаксис, аналогичный синтаксису таблица.столбец):

DECLARE
   l_name employee_pkg.fullname_t; 
   employee_id_in employees.employee_id%TYPE := 1;
BEGIN
   l_name := employee_pkg.fullname (employee_id_in);
END;

Переменная l_name объявляется с новым типом данных, а для присваивания ей нужного значения вызывается соответствующая функция этого же пакета. Таким образом, формула построения полного имени и SQL-запрос вынесены из кода приложения в специальный «контейнер» для всей функциональности, относящейся к обработке данных о сотруд­никах. Код стал проще и лаконичнее. Если потребуется изменить формулу построения полного имени или увеличить размер его типа данных, достаточно внести соответству­ющие изменения в спецификацию или тело пакета и перекомпилировать его код.

Реализация employee_pkg выглядит так:

PACKAGE BODY employee_pkg
AS
   FUNCTION fullname (
      last_in employee.last_name%TYPE,
      first_in employee.first_name%TYPE
   )
      RETURN fullname_t
   IS
   BEGIN
      RETURN last_in || ', ' || first_in;
   END;

   FUNCTION fullname (employee_id_in IN employee.employee_id%TYPE)
      RETURN fullname_t
   IS
      retval fullname_t;
   BEGIN
      SELECT fullname (last_name, first_name) INTO retval
      FROM employee
      WHERE employee_id = employee_id_in;

      RETURN retval;
   EXCEPTION
      WHEN NO_DATA_FOUND THEN RETURN NULL;

      WHEN TOO_MANY_ROWS THEN errpkg.record_and_stop;
   END;
END employee_pkg;

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

 

Строки Описание
3-11 Функция-«обертка» для определения формата полного имени «ФАМИЛИЯ, ИМЯ»
13-27 Типичный запрос на выборку одной строки, выполняемый с помощью неявного курсора
18 Вызов функции fullname, возвращающей комбинацию двух компонентов имени

 

Если теперь пользователь потребует изменить формат представления полного имени, нам не придется искать по всему приложению все вхождения || ', ' || — для установки нового формата достаточно модифицировать в пакете employee_pkg функцию fullname.

 

Основные концепции пакетов

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

  •  Сокрытие информации. Сокрытие информации о системе или приложении обычно преследует две цели. Во-первых, возможности человека по работе со сложными системами ограничены. Исследования показали, что у среднего «мозга» при запо­минании даже семи (плюс/минус двух) элементов в группе возникают проблемы. Таким образом, пользователь (или разработчик) освобождается от необходимости вникать в ненужные подробности и может сосредоточиться на действительно важ­ных аспектах. Во-вторых, сокрытие информации препятствует доступу к закрытым сведениям. Например, разработчик может вызвать в своем приложении готовую функцию для вычисления некоторого значения, но при этом формула вычислений может быть секретной. Кроме того, в случае изменения формулы все модификации будут вноситься только в одном месте.
  •  Общие и приватные элементы. Концепция общих и приватных элементов тесно связана с концепцией сокрытия информации. Общедоступный код определяется в спецификации пакета и доступен любой схеме, обладающей для этого пакета привилегией EXECUTE. Приватный код виден только в пределах пакета. Внешние про­граммы, работающие с пакетом, не видят приватный код и не могут использовать его.

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

На ранних стадиях развития программы в теле пакета также могут реализоваться в виде «заглушек» с минимальным объемом кода, необходимым для компиляции пакета. Этот прием позволяет сосредоточиться на интерфейсах программы и их взаимных связях.

  • Спецификация пакета. Она содержит определения всех общедоступных элементов пакета, на которые можно ссылаться извне. Спецификация напоминает большой раздел объявлений; она не содержит блоков PL/SQL или исполняемого кода. Из хо­рошо спроектированной спецификации разработчик может получить всю необходи­мую для использования пакета информацию и ему никогда не придется заглядывать «за интерфейс» (то есть в тело пакета, содержащее реализацию его компонентов).
  • Тело пакета. Здесь находится весь код, который необходим для реализации элемен­тов, определенных в спецификации пакета. Тело может содержать отсутствующие в спецификации личные элементы, на которые нельзя ссылаться извне пакета, в частности объявления переменных и определения пакетных модулей. Кроме того, в теле пакета может находиться исполняемый (инициализационный) раздел, который выполняется только один раз для инициализации пакета.
  • Инициализация. Концепция инициализации хорошо известна любому програм­мисту, однако в контексте пакетов она имеет особое значение. В данном случае инициализируется не отдельная переменная, а весь пакет путем выполнения кода произвольной сложности. При этом Oracle следит за тем, чтобы пакет инициализи­ровался только один раз за сеанс.
  • Постоянство в течение сеанса. Концепция постоянства (или сохраняемости) тоже хорошо знакома программистам. Когда вы подключаетесь к Oracle и выполняете программу, присваивающую значение переменной уровня пакета (то есть пере­менной, объявленной в пакете вне содержащихся в нем программ), эта переменная сохраняет значение в течение всего сеанса, даже если выполнение присвоившей его программы завершается.

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

Именно пакеты обеспечивают поддержку структур данных с сеансовым постоянством в языке PL/SQL.

 

Графическое представление приватности

Различия между общедоступными и приватными элементами пакета дают разработчикам PL/SQL беспрецедентные средства управления структурами данных и программами. Грэди Буч предложил визуальное средство описания этих аспектов пакета (которое было вполне естественно названо диаграммой Буча).

Взгляните на рис. 1. Обратите внимание на надписи «Внутренняя часть» и «Внешняя часть». Первая часть содержит тело пакета (внутренняя реализация пакета), а вторая — все программы, написанные вами и не являющиеся частью пакета (внешние программы).

 

Диаграмма Буча с общедоступными и приватными элементами пакета

Рис. 1. Диаграмма Буча с общедоступными и приватными элементами пакета

 

Несколько выводов, следующих из диаграммы Буча:

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

 

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

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

OraCool аватар
OraCool ответил в теме #9509 08 окт 2019 05:41
Myk, да! Спасибо за проделанную работу!)
Кроме своей уникальной возможности сохранения состояния во время сеанса, па­кеты PL/SQL обладают преимуществами из области производительности. Большая часть логики приложения должна размещаться в телах пакетов.
Myk аватар
Myk ответил в теме #9259 14 окт 2018 07:59
Примеры хороши! Даже новичок разберется теперь как писать package и где лучше применять!