Пакет 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. Диаграмма Буча с общедоступными и приватными элементами пакета
Несколько выводов, следующих из диаграммы Буча:
- Внешние программы не могут пересекать границу внутренней реализации; иначе говоря, внешняя программа не может обращаться или вызывать элементы, определенные в теле пакета. Это приватные элементы, невидимые за пределами пакета.
- Элементы, определенные в спецификации пакета («Внешняя часть» на диаграмме), располагаются по обе стороны от границы между внутренней и внешней частью. Такие программы могут вызываться внешней программой (из внешней части), они доступны для приватных программ и в свою очередь могут вызывать или обращаться ко всем остальным элементам пакета.
- Общедоступные элементы пакета предоставляют единственный путь к внутренней части. В этом отношении спецификация пакета действует как управляющий механизм для пакета в целом.
- Если окажется, что объект, ранее бывший приватным (например, модуль или курсор), должен стать общедоступным, просто добавьте его в спецификацию и перекомпилируйте пакет. После этого объект станет доступным за пределами пакета.