Что такое пакет в oracle
Oracle PL/SQL •MySQL •MariaDB •SQL Server •SQLite
Базы данных
Oracle PL/SQL Пакеты
В этом учебном пособии вы узнаете, как создавать пакеты и тела пакетов в Oracle PL/SQL с синтаксисом и примерами.
Описание
В Oracle PL/SQL набор элементов: процедур, функций, определения типов; объявления переменных, констант можно объединить в пакет. После написания пакет PL/SQL компилируется, а затем сохраняется в базе данных Oracle, где его содержимое может использоваться многими приложениями.
Что такое пакет Oracle PL/SQL?
Для создания пакетов используйте оператор CREATE PACKAGE.
Синтаксис
Синтаксис CREATE PACKAGE в Oracle PL/SQL:
Спецификация пакета содержит публичные объявления, которые видны вашему приложению. Вы должны объявить подпрограммы в конце спецификации после всех других элементов (кроме прагм, которые вызывают конкретную функцию; такие прагмы должны следовать спецификации функции).
Тело пакета содержит детали реализации и приватные объявления, которые скрыты от вашего приложения. За декларативной частью тела пакета следует необязательная часть инициализации, которая обычно содержит операторы, которые инициализируют переменные пакета.
Пример пакета Oracle PL/SQL
В приведенном ниже примере, вы определяете тип запись, курсор и две процедуры по трудоустройству. Обратите внимание, что процедура hire_employee использует последовательность базы данных empno_seq и функцию SYSDATE для вставки нового номера сотрудника и дату приема на работу соответственно.
Пакеты в PL/SQL: предназначение и применение на практике
Для чего нужны пакеты в PL/SQL?
Пакеты — очень важная составная часть языка PL/SQL, краеугольный камень любого сложного проекта. Чтобы это понять, необходимо рассмотреть основные преимущества пакетов:
Прежде чем приступать к рассмотрению преимуществ пакетов и описанию синтаксиса их определения, необходимо сделать одно важное замечание. Всегда стройте приложение на основе пакетов; избегайте отдельных процедур и функций. Даже если вам сейчас кажется, что для реализации определенной возможности достаточно одной процедуры или функции, в будущем к ней почти наверняка добавятся еще несколько. Когда вы поймете, что их лучше объединить в пакет, придется искать все вызовы процедур и функций и добавлять к ним префикс с именем пакета. Используйте пакеты с самого начала, избавьте себя от будущих проблем!
Демонстрация возможностей пакетов PL/SQL
Пакет состоит из двух частей — спецификации и тела. Спецификация является обязательной частью и определяет, как разработчик может использовать пакет: какие программы можно вызывать, какие курсоры открывать и т. д. Тело пакета — необязательная, но почти всегда присутствующая часть; она содержит код перечисленных в спецификации программ (и возможно, курсоров), а также другие необходимые элементы кода. Предположим, нам нужна программа для получения полного имени сотрудника, которое хранится в базе данных в виде двух отдельных элементов: фамилии и имени. На первый взгляд кажется, что задача решается просто:
Однако этот вроде бы тривиальный код обладает рядом скрытых недостатков:
Приложения должны строиться таким образом, чтобы избежать жесткого кодирования подобных элементов. Определение типа данных для полного имени, представление, запрос к базе данных и т. п. должны кодироваться один раз в строго определенном месте и быть доступны из любой точки приложения. Таким местом и является пакет. Рассмотрим следующую спецификацию пакета:
Фактически здесь перечисляются различные элементы, которые должны использоваться разработчиками. Важнейшие элементы кода представлены в следующей таблице.
Но прежде чем рассматривать реализацию пакета, давайте перепишем исходный блок кода таким образом, чтобы в нем использовались элементы пакета (обратите внимание на точечный синтаксис, аналогичный синтаксису таблица.столбец):
Переменная l_name объявляется с новым типом данных, а для присваивания ей нужного значения вызывается соответствующая функция этого же пакета. Таким образом, формула построения полного имени и SQL-запрос вынесены из кода приложения в специальный «контейнер» для всей функциональности, относящейся к обработке данных о сотрудниках. Код стал проще и лаконичнее. Если потребуется изменить формулу построения полного имени или увеличить размер его типа данных, достаточно внести соответствующие изменения в спецификацию или тело пакета и перекомпилировать его код.
Реализация employee_pkg выглядит так:
В следующей таблице перечислены важные элементы этого кода.
Строки | Описание |
3-11 | Функция-«обертка» для определения формата полного имени «ФАМИЛИЯ, ИМЯ» |
13-27 | Типичный запрос на выборку одной строки, выполняемый с помощью неявного курсора |
18 | Вызов функции fullname, возвращающей комбинацию двух компонентов имени |
Основные концепции пакетов
Прежде чем переходить к подробному изучению синтаксиса и структуры пакетов, следует изучить некоторые концепции пакетов:
Приступая к разработке пакета, вы решаете, какие из его элементов будут общими, а какие — приватными. Кроме того, тело пакета можно скрыть от других схем/разработчиков. В таком случае пакет используется для сокрытия деталей реализации программ. Это особенно полезно для изоляции переменных компонентов приложения — фрагментов кода, зависящих от платформы, часто меняющихся структур данных и временных обходных решений.
На ранних стадиях развития программы в теле пакета также могут реализоваться в виде «заглушек» с минимальным объемом кода, необходимым для компиляции пакета. Этот прием позволяет сосредоточиться на интерфейсах программы и их взаимных связях.
Также существует концепция сеансового постоянства. Если я подключаюсь к базе данных Oracle (создаю сеанс) и выполняю программу, которая присваивает значение пакетной переменной (то есть переменной, объявленной в спецификации или теле пакета, за пределами всех входящих в него программ), то эта переменная продолжает существовать на всем протяжении сеанса и сохраняет свое значение даже при завершении программы, выполнившей присваивание.
Именно пакеты обеспечивают поддержку структур данных с сеансовым постоянством в языке PL/SQL.
Графическое представление приватности
Различия между общедоступными и приватными элементами пакета дают разработчикам PL/SQL беспрецедентные средства управления структурами данных и программами. Грэди Буч предложил визуальное средство описания этих аспектов пакета (которое было вполне естественно названо диаграммой Буча).
Взгляните на рис. 1. Обратите внимание на надписи «Внутренняя часть» и «Внешняя часть». Первая часть содержит тело пакета (внутренняя реализация пакета), а вторая — все программы, написанные вами и не являющиеся частью пакета (внешние программы).
Рис. 1. Диаграмма Буча с общедоступными и приватными элементами пакета
Несколько выводов, следующих из диаграммы Буча:
Правила построения пакетов (package) в PL/SQL на примерах
Структура пакета PL/SQL выглядит очень просто, но эта простота обманчива. Хотя синтаксис и правила построения пакетов вы изучите очень быстро, полное понимание нюансов реализации придет далеко не сразу. В этом разделе рассматриваются правила построения пакетов, а далее рассказывается, в каких ситуациях пакеты особенно эффективны.
Чтобы создать пакет, необходимо написать его спецификацию и почти всегда — тело. При этом нужно решить, какие элементы пакета будут указаны в спецификации, а какие скрыты в теле. В пакет также можно включить блок кода, который база данных Oracle будет выполнять при инициализации пакета.
Спецификация пакета PL/SQL
Спецификация пакета содержит список всех доступных элементов и предоставляет разработчику информацию, необходимую для использования пакета в приложениях. Ее часто называют программным интерфейсом — API (Application Programming Interface). Чтобы узнать, как применять описанные в спецификации элементы, разработчику не нужно изучать код, находящийся в теле пакета.
При разработке спецификации пакета необходимо руководствоваться следующими правилами:
Для демонстрации этих правил рассмотрим простую спецификацию пакета:
Как видите, пакет имеет почти такую же структуру спецификации, как раздел объявлений блока PL/SQL. Единственное отличие заключается в том, что спецификация не может содержать кода реализации.
Тело пакета
Тело пакета содержит весь код, необходимый для реализации спецификации пакета. Оно не является стопроцентно необходимым; примеры спецификаций пакетов без тела приведены в разделе «Когда используются пакеты». Тело пакета необходимо в том случае, если истинны хотя бы некоторые из следующих условий:
Со структурной точки зрения тело пакета очень похоже на определение процедуры. Несколько правил, специфических для тел пакетов:
Ниже приведена моя реализация тела favorites_pkg :
Другие примеры тел пакетов приведены в разделе «Когда используются пакеты».
Инициализация пакетов
Пакет может содержать структуры данных, сохраняющиеся на протяжении всего сеанса (см. раздел «Работа с данными пакета»). Когда в ходе сеанса впервые происходит обращение к пакету (вызывается объявленная в нем программа, считывается или записывается значение переменной либо используется объявленный в пакете тип), Oracle инициализирует его, выполняя следующие действия:
Oracle выполняет все эти действия только один раз за сеанс и только тогда, когда возникнет непосредственная необходимость в этой информации.
Пакет может быть повторно инициализирован в ходе сеанса, если он был перекомпилирован с момента последнего использования или был выполнен сброс состояния всего сеанса, на что указывает ошибка ORA-04068.
PL/SQL автоматически определяет, когда должен выполняться код инициализацион- ного раздела. Это означает, что нет необходимости вызывать его явно и можно быть уверенными в том, что он будет выполнен только один раз. Когда следует использовать инициализационный раздел? Ниже описаны некоторые возможные причины.
Выполнение сложной логики инициализации
Конечно, значения по умолчанию могут присваиваться пакетным данным прямо в команде объявления. Тем не менее у этого подхода есть несколько потенциальных недостатков:
Инициализация данных в инициализационном разделе обладает рядом преимуществ перед присваиванием значений по умолчанию. В частности, в исполняемом разделе вы обладаете полной гибкостью в определении, структуре и документировании ваших действий, а при возникновении исключения вы можете обработать его в разделе исключений инициализационного раздела.
Кэширование статической сеансовой информации
Другая причина для включения инициализационного раздела в пакет — кэширование статической информации, то есть остающейся неизменной на протяжении сеанса. Если значения данных не изменяются, зачем мириться с лишними затратами на запросы или повторное вычисление этих данных?
Кроме того, если вы хотите принять меры к тому, чтобы информация читалась в сеансе только один раз, инициализационный раздел становится идеальным автоматизированным решением.
При работе с кэшированными пакетными данными приходится учитывать важный компромисс между затратами памяти и вычислительных мощностей. Кэшируя данные в пакетных переменных, можно улучшить время выборки данных. Для этого данные размещаются «ближе» к пользователю, в области PGA каждого сеанса. При 1000 сеансах в системе существует 1000 копий кэшированных данных. Кэширование снижает нагрузку на процессор, но увеличивает затраты памяти — причем иногда весьма значительно.
За дополнительной информацией по этой теме обращайтесь к разделу «Кэширование статических данных сеанса для ускорения работы приложения».
Предотвращение побочных эффектов при инициализации
Избегайте присваивания значений глобальных данных в других пакетах (и вообще любых значений в других пакетах, если уж на то пошло). Эта защитная мера поможет предотвратить хаос при выполнении кода и потенциальную путаницу у программистов, занимающихся сопровождением. Код инициализационного раздела должен быть сконцентрирован на текущем пакете. Помните, что этот код выполняется тогда, когда ваше приложение в первый раз пытается использовать элемент пакета. Пользователи не должны сидеть сложа руки, пока пакет выполняет высокозатратные вычисления, которые можно вынести в другие пакеты или триггеры приложения. Пример кода, которого следует избегать:
Если ваши требования к инициализации отличны от представленных нами, рассмотрите альтернативу для инициализационного раздела — например, сгруппируйте стартовые команды в процедуре приложения. Присвойте процедуре содержательное имя (например, init_environment); затем в нужной точке процесса инициализации вызовите процедуру init_environment для настройки сеанса.
Ошибки при инициализации
Инициализация пакета проходит в несколько этапов: объявление данных, присваивание значений по умолчанию, выполнение инициализационного раздела (если он присутствует). А если произойдет ошибка, приводящая к сбою процесса инициализации? Оказывается, даже если пакет не может завершить свои действия по инициализации, база данных помечает пакет как инициализированный и не пытается снова выполнять стартовый код в этом сеансе. Чтобы убедиться в этом, рассмотрим следующий пакет:
Допустим, я подключаюсь к SQL*Plus и пытаюсь выполнить функцию valerr.get (первый раз в этом сеансе). Вот что я увижу:
Как интересно! Строка «Before I show you v. » вообще не выводится; более того, эта команда не выполняется. Ошибка происходит при первом вызове пакетной функции, но не при втором и всех последующих вызовах. Перед нами одна из классических «невоспроизводимых ошибок», а в мире PL/SQL это типичная причина подобных проблем: сбой в ходе инициализации пакета.
Подобные ошибки усложняют диагностику. Чтобы снизить риск таких ошибок и упростить их обнаружение, лучше всего переместить присваивание значений по умолчанию в инициализационный раздел, чтобы раздел исключений мог корректно обрабатывать ошибки и сообщать об их вероятных причинах:
Вы даже можете стандартизировать структуру пакетов и потребовать обязательного включения процедуры инициализации, чтобы разработчики группы не забывали об этой проблеме:
Когда используются пакеты (package) PL/SQL на примере
Итак, в прошлых моих блогах мы рассмотрели синтаксис, основные правила и нюансы построения пакетов. Теперь можно вернуться к списку ситуаций, в которых пакеты PL/SQL следует применять, и рассмотреть их более подробно.
Ниже каждая из этих причин рассматривается более подробно.
Инкапсуляция доступа к данным
Вместо того чтобы заставлять разработчиков самостоятельно писать команды SQL, предоставьте интерфейс к этим командам. Это одна из самых важных причин для построения пакетов, но она применяется относительно редко.
При таком подходе разработчики PL/SQL вместо команд SQL обычно включают в свои приложения заранее определенный, протестированный и оптимизированный код, который выполняет всю работу за них; например, процедуру add (перегруженную для поддержки записей), которая выдает команду INSERT и следует стандартным правилам обработки ошибок, функцию для выборки одной записи по первичному ключу и разнообразные курсоры для обработки стандартных запросов к структуре данных (которой может быть одна таблица или «бизнес-сущность» из нескольких таблиц).
Если вы выберете этот путь, разработчикам не нужно будет разбираться, как объединить три или шесть нормализованных таблиц для получения нужного набора данных. Они просто выбирают нужный курсор, а анализ данных оставляется кому-то другому. Им не нужно думать, что делать, если при попытке вставки строка уже существует — процедура уже содержит всю необходимую логику.
Вероятно, самое серьезное преимущество такого подхода заключается в том, что при изменении структуры данных проблемы с обновлением кода приложения сводятся к минимуму и централизуются. Человек, хорошо знающий таблицу или объектный тип, вносит необходимые изменения в одном пакете, а эти изменения затем более или менее автоматически распространяются на всех программы, зависящие от этого пакета.
Давайте посмотрим, как использование пакетов отражается на коде. Файл givebonusi. sp на сайте книги содержит процедуру, которая начисляет одинаковые премии всем работникам заданного отдела, — но только при условии, что стаж работника в компании составляет не менее 6 месяцев. Ниже приведены части программы give_bonus с кодом SQL (полная реализация содержится в файле givebonusi.sp ):
Сравните с альтернативным решением, полный код которого содержится в файле givebonus2.sp :
В следующей таблице объясняются изменения, внесенные во второй версии.
В целом команды SQL были исключены из программы и заменены вызовами процедур и функций, предназначенных для многократного использования. В моем приложении такое решение оптимизирует SQL и способствует более эффективному написанию более надежного кода. Построение (или генерирование) таких пакетов ни в коей мере не является тривиальным делом, я и понимаю, что большинство читателей вряд ли захотят или смогут использовать «чистый» инкапсулированный подход. Однако многие преимущества инкапсуляции данных могут использоваться и без полной переработки стиля программирования. Как минимум я рекомендую:
Исключение жесткого кодирования литералов
Практически в каждом приложении используются всевозможные «волшебные значения» — литералы, имеющие особый смысл для системы. Например, это могут быть коды типов или граничные значения для проверки данных. Конечно, пользователи скажут вам, что волшебные значения никогда не изменяются. «В моем балансе всегда будет ровно 25 позиций», — говорит один. «Родительская компания всегда будет называться ATLAS HQ», — клянется другой. Не верьте клятвам и никогда не фиксируйте их в своих программах. Возьмем следующие команды IF:
Тот, кто пишет подобный код, сам напрашивается на неприятности. Ваша жизнь намного упростится, если вы создадите пакет с именованными константами:
С таким пакетом две предшествующие команды IF принимают следующий вид:
Если в будущем какое-либо из «волшебных значений» изменится, достаточно изменить соответствующую константу в конфигурационном пакете. Вносить изменения в остальных модулях не нужно. Практически в каждом приложении из тех, которые я рецензировал (и некоторые из тех, что я написал), ошибочно включались жестко закодированные «волшебные значения». В каждом случае разработчику приходилось вносить множественные изменения в программы — как в фазе разработки, так и в фазе сопровождения. Иногда это создает проблемы, иногда оборачивается сущим кошмаром; я не могу выразить словами, насколько важно консолидировать все «волшебные значения» в одном или нескольких пакетах.
Наконец, если вам доведется выбирать литеральные значения, которые планируется скрыть за константами, старайтесь использовать невероятные значения, которые вряд ли будут использоваться как литералы. Предположим, процедура должна возвращать индикатор состояния: успех или неудача? Типичные значения таких флагов — 0 и 1, S и F и т. д. Тем не менее у таких значений есть один недостаток: они коротки и интуитивно понятны, поэтому у недисциплинированного программиста возникает соблазн «смухлевать» и напрямую использовать литералы в коде. Возьмем следующий пример:
Скорее всего, с таким определением вы столкнетесь с использованием big_stuff следующего вида:
С другой стороны, если спецификация пакета выглядит так:
вы никогда не увидите такой код:
Он выглядит просто неприлично.
Устранение недостатков встроенных функций
Вместо того чтобы заполнять страницы книги примерами, я перечислю файлы нескольких пакетов, размещенных на сайте книги. Эти примеры демонстрируют использование пакетов, а также ряд других полезных возможностей. Я рекомендую просмотреть файлы *.pkg на сайте — вы найдете в них код, который может пригодиться в ваших приложениях. С чего стоит начать?
Группировка логически связанных функций
Если ваша программа содержит десяток процедур и функций, связанных с конкретной функциональностью или аспектом вашего приложения, разместите их в пакете, чтобы упростить управление этим кодом (и его поиск). Это особенно важно при программировании бизнес-правил приложения, в реализации которых следует соблюдать некоторые важные правила:
Прежде чем браться за построение приложения, сконструируйте набор пакетов, инкапсулирующих все его правила. Иногда эти правила являются частью большего пакета — например, пакета инкапсуляции таблиц. В других случаях можно создать пакет, который не содержит ничего, кроме ключевых правил, как в следующем примере:
Конечно, не вся «логически связанная функциональность» имеет отношение к бизнесправилам. Допустим, мне нужно расширить возможности встроенных функций PL/ SQL для работы со строками. Вместо того чтобы создавать 12 разных функций, я создаю пакет, размещаю все функции в этом пакете и сообщаю другим разработчикам, как обратиться к этой функциональности.
Кэширование статических данных сеанса для ускорения работы приложения
Используйте данные пакетов для улучшения времени отклика приложения посредством кэширования статических данных (без повторных запросов). Это можно сделать на нескольких уровнях. Для каждого варианта в следующем списке я привожу несколько полезных примеров кода, доступных на сайте книги.
Кэширование на базе пакетов — всего лишь одна из разновидностей кэширования, доступных для разработчиков PL/SQL.
Если вы решите использовать кэширование уровня пакета, помните, что данные кэшируются по отдельности для каждого сеанса, обращающегося к пакету (в глобальной области PGA базы данных Oracle). Таким образом, если кэш занимает 2 Мбайт и в системе существует 1000 одновременно подключенных сеансов, вы расходуете 2 Гбайт памяти своей системы — кроме всей остальной памяти, расходуемой базой данных.
Объектно-ориентированное программирование в PL/SQL
Пакеты языка PL/SQL
Создание пакета
Подпрограммы
Подпрограммами называются именованные блоки PL/SQL, которые могут иметь параметры.
Определение процедуры может иметь следующее формальное описание:
Параметры в списке параметров определяются как:
Для определения соответствия между формальными и фактическими параметрами предусмотрены два типа нотаций:
При позиционной нотации порядок формальных и фактических параметров должен совпадать.
При именованной нотации порядок указания параметров не имеет значения, но перед значением параметра указывается имя формального параметра и символ =>. Список параметров может содержать оба типа нотаций одновременно, но именованная нотация располагается только в конце списка.
Процедура имеет две части:
Определение функции может иметь следующее формальное описание:
Параметры в списке параметров определяются как:
Перегружаемые подпрограммы можно применять для реализации одних и тех же действий над переменными различных типов. Для перегружаемых подпрограмм компилятор будет искать подпрограмму с совпадающим списком фактических параметров только до тех пор, пока не просмотрит все перегружаемые подпрограммы данного блока. И только в том случае, если перегружаемых подпрограмм с указанным именем в данном блоке нет, то компилятор продолжит поиск во внешнем блоке.
Рекурсивные и взаимно рекурсивные вызовы подпрограмм
При каждом рекурсивном вызове :
Следующая функция fibonati1 реализует формирование последовательности Фибоначчи с применением рекурсии:
Вместо рекурсивного способа можно использовать и итерационный способ, который менее нагляден, но требует и меньше памяти, и быстрее выполняется.
Так, следующая функция fibonati2 реализует формирование последовательности Фибоначчи итерационным способом: