Язык Java
изначально был спроектирован и продвигался компанией Sun Microsystems
. В наши дни его продвигают практически все, кроме Microsoft
. Java
предоставляет исключительно разнообразный набор программных средств, многие из которых недоступны в PL/SQL
напрямую. В этой статье представлены основы создания и использования хранимых процедур Java
в Oracle
. Также вы узнаете, как создавать и пользоваться функциональностью JSP из PL/SQL
.
Oracle и Java
Начиная с Oracle8i
Database
в поставку Oracle
Database
Server
входит виртуальная машина Java
, обеспечивающая эффективное выполнение программ Java в пространстве памяти сервера. Также в комплект поставки Oracle
включаются многие базовые библиотеки классов Java
, которые становятся не только серьезным оружием в арсенале программиста, но и серьезной темой для книги по PL/SQL
. Вот почему в этой книге мы ограничимся следующими целями:
- Предоставить читателю информацию, необходимую для загрузки классов
Java
в базе данныхOracle
, управления этими новыми объектами базы данных и их публикации для использования вPL/SQL
. - Предоставить вводный курс по построению классов
Java
, чтобы вы смогли создавать простые классы для использования нижележащей функциональностиJava
.
Как правило, создание и предоставление доступа к хранимым процедурам Java
проходит следующим образом:
- Разработчик пишет исходный код
Java
. При этом можно использовать любой удобный текстовый редактор или интегрированную среду разработки (например,Oracle JDeveloper
). - Откомпилируйте классы
Java
; при желании упакуйте их в файлы .jar
. Эта операция также может выполняться интегрированной средой или компилятором командной строкиSun javac
. (Строго говоря, этот шаг не обязателен, потому что вы можете загрузить исходный код вOracle
и воспользоваться встроенным компиляторомJava
.) - Загрузите классы
Java
вOracle
программой командной строки loadjava или командойCREATE JAVA
. - Опубликуйте методы класса
Java
; для этого напишите «обертки»PL/SQL
для вызова кодаJava
. - Предоставьте необходимые привилегии для обертки
PL/SQL
. - Вызовите программы
PL/SQL
в одной из доступных сред (рис. 1).
Рис. 1. Обращение к JSP
из базы данных Oracle
Oracle
предоставляет разнообразные компоненты и команды для работы с Java
. Некоторые из них перечислены в табл. 1.
Таблица 1. Компоненты и команды Oracle
для Java
Компонент | Описание |
Aurora JVM | Виртуальная машина Java , реализованная Oracle в сервере базы данных |
loadjava | Программа командной строки для загрузки элементов кода Java (классы, файлы .jar и т. д.) в базу данных Oracle |
dropjava | Программа командной строки для удаления элементов кода Java (классы, файлы .jar и т. д.) из базы данных Oracle |
CREATE JAVA DROP JAVA, ALTER JAVA | Команды DDL , выполняющие те же операции, что и loadjava и dropjava |
DBMS_JAVA | Встроенный пакет, содержащий набор инструментов для настройки параметров и других аспектов JVM |
Все эти операции и компоненты будут рассмотрены в этой статье. Более подробную информацию о работе с Java
в базе данных Oracle
можно найти в книге Дональда Бейлза Java Programming with Oracle JDBC. За дополнительной информацией о Java
обращайтесь к документации Sun Microsystems
и к книгам издательства O’Reilly из серии, посвященной Java
(а также к другим книгам, которые я рекомендую далее в этой статье). Более подробная документация об использовании Java
с Oracle
приведена в учебных руководствах Oracle Corporation
.
Подготовка к использованию Java в Oracle
Прежде чем вызывать методы Java
из программ PL/SQL
, вы должны:
- убедиться в том, что поддержка
Java
включена в установкуOracle Database Server
; - построить и загрузить элементы кода и классы
Java
; - в некоторых случаях — принять меры к тому, чтобы вашей учетной записи пользователя
Oracle
были предоставлены разрешения, относящиеся кJava
.
Установка Java
Поддержка Java
может устанавливаться с сервером Oracle
— а может и не устанавливаться в зависимости от версии Oracle
и решений, принятых администратором базы данных в ходе установки Oracle
. Чтобы проверить, установлена ли поддержка Java
, выполните следующий запрос:
SELECT COUNT(*)
FROM all_objects
WHERE object_type LIKE 'JAVA%';
Если результат равен 0, то поддержка Java
определенно не установлена; попросите своего администратора выполнить сценарий с именем $ORACLE_HOME/javavm/mstall/initjvm.sql
.
Вы как разработчик, вероятно, захотите строить и тестировать Java
-программы на вашей рабочей станции, а для этого вам потребуется пакет JDK
(Java Development
Kit
). У вас есть два варианта: либо загрузите JDK
самостоятельно по адресу http://java.sun.com/, либо, если вы используете стороннюю среду разработки (такую, как Oracle JDeveloper
), возможно, вы сможете использовать прилагаемую версию JDK
. Будьте внимательны и обратите особое внимание на точное совпадение номера версии JDK
.
При загрузке Java
с сайта Sun
вам придется выбирать между множеством разных сокращений и версий. Лично у меня остались неплохие впечатления от Java
2 Standard
Edition
(J2SE
) с пакетом Core Java
— вместо пакета Desktop
, включающего набор ненужных мне средств построения графического интерфейса. Также необходимо выбрать между JDK
и JRE
(Java Runtime Engine
). Всегда выбирайте JDK
, если вы собираетесь что-то компилировать! В том, что касается выбора правильной версии, я бы предложил ориентироваться на версию сервера Oracle
. В следующей таблице приведены некоторые рекомендации.
Версия Oracle | Версия JDK |
Oracle8i Database (8.1.5) | JDK 1.1.6 |
Oracle8i Database (8.1.6 or later) | JDK 1.2 |
Oracle9i Database | J2SE 1.3 |
Oracle Database 10g Release 1 | J2SE 1.4.1 |
Oracle Database 10g Release 2 | J2SE 1.4.2 |
Если вам приходится поддерживать сразу несколько версий сервера Oracle
, выберите последнюю версию и будьте внимательны с используемой функциональностью.
Еще одно неочевидное обстоятельство, о котором необходимо знать: если ваша программа Java
не компилируется, убедитесь в том, что в переменную окружения CLASSPATH
были включены ваши классы, а также классы, предоставляемые Oracle
.
Построение и компиляция кода Java
У многих разработчиков PL/SQL
(в том числе и у меня) опыт работы с объектно-ориентированными языками оставляет желать лучшего, поэтому освоить Java
может быть непросто. За то непродолжительное время, в течение которого я изучал и использовал Java
, я пришел к следующим выводам:
- Освоить синтаксис, необходимый для построения простых классов в
Java
, не так сложно. - Начать применять код
Java
вPL/SQL
тоже несложно, но... - Написание полноценных объектно-ориентированных приложений с использованием Java потребует существенных усилий и изменения менталитета для разработчиков
PL/SQL
!
О разных аспектах Java
написано много (очень, очень много!) книг, среди которых немало просто превосходных. Я рекомендую обратить особое внимание на следующие книги:
- The Java Programming Language, авторы Кен Арнольд (Ken Arnold), Джеймс Гослинг (James Gosling) и Дэвид Холмс (David Holmes) (издательство Addison-Wesley). Джеймс Гослинг — создатель языка
Java
, поэтому, как и следовало ожидать, книга весьма полезна. Она написана простым, понятным языком и хорошо обучает основам языка. - Java in a Nutshell, автор Дэвид Фленаган (David Flanagan) (издательство O’Reilly). Эта очень популярная и часто обновляемая книга содержит короткий, но превосходный вводный курс. За ним следует краткий справочник всех основных элементов языка, удобный и снабженный множеством перекрестных ссылок.
- Философия Java, автор Брюс Экель (издательство «Питер»).
В книге доступно и нестандартно излагаются концепции объектно-ориентированного программирования (кстати, ее можно бесплатно загрузить с сайта MindView
). Если вам близок стиль изложения моей книги, то и Философия Java
наверняка понравится. Позднее в этом блоге, когда я буду демонстрировать вызов методов Java
из PL/SQL
, также будет приведено пошаговое описание создания относительно простых классов. Во многих ситуациях этого обсуждения будет вполне достаточно для решения практических задач.
Назначение разрешений для разработки и выполнения Java-кода
До выхода версии 8.1.6 в Oracle
использовалась несколько иная модель безопасности Java
, поэтому ниже две модели будут рассмотрены по отдельности.
Безопасность Java
для Oracle
(версии, предшествующие 8.1.5)
В ранних выпусках Oracle8i Database
(до версии 8.1.6) поддерживалась относительно простая модель безопасности Java
. По сути было всего две роли базы данных, которые мог предоставить администратор базы данных:
-
JAVAUSERPRIV
— относительно небольшой набор разрешенийJava
, включая просмотр свойств. -
JAVASYSPRIV
— самые важные разрешения, включая обновление защищенных пакетов.
Например, чтобы разрешить Скотту выполнение любых операций, связанных с Java
, я должен был выдать следующую команду из учетной записи SYSDBA
:
GRANT JAVASYSPRIV TO scott;
А если я хотел ограничить возможности операций, которые он мог выполнять с Java, выполнялась следующая команда:
GRANT JAVAUSERPRIV TO scott;
Например, для создания файла средствами Java
потребуется роль JAVASYSPRIV
; для чтения или записи в файл достаточно роли JAVAUSERPRIV
. За дополнительной информацией о том, какие привилегии Java
соответствуют разным ролям Oracle
, обращайтесь к руководству Oracle Java Developer’s Guide
.
При инициализации виртуальная машина Java
устанавливает экземпляр java.lang
. SecurityManager
(менеджер безопасности Java
). Oracle
использует их в сочетании со средствами безопасности Oracle Database
для определения того, кому разрешен вызов тех или иных методов Java
.
Если пользователь, не обладающий достаточными привилегиями, попробует выполнить недопустимую операцию, виртуальная машина Java
инициирует исключение java.lang
. SecurityException
. А вот что вы увидите в SQL*Plus
:
ORA-29532: Java call terminated by uncaught Java exception:
java.lang.SecurityException
Выполнение методов Java
в базе данных может создать различные проблемы, связанные с безопасностью, — особенно при взаимодействиях с файловой системой на стороне сервера или другими ресурсами операционной системы. При проверке операций ввода/ вывода Oracle
следует двум правилам:
- Если динамическому идентификатору была назначена привилегия
JAVASYSPRIV
, то менеджер безопасности разрешает выполнение операции. - Если динамическому идентификатору была назначена привилегия
JAVAUSERPRIV
, то менеджер безопасности проверяет действительность операции по тем же правилам, что и для пакетаPL/SQL
UTL_FILE
. Другими словами, файл должен находиться в каталоге (или подкаталоге), заданном параметромUTL_FILE_DIR
в файле инициализации базы данных.
Безопасность Java в Oracle (версия 8.1.6 и выше)
Начиная с версии 8.1.6 в виртуальной машине Java
в Oracle
появилась поддержка безопасности Java
2, в которой разрешения предоставляются на уровне классов — гораздо более сложный и точный механизм управления безопасностью. В этом разделе приводятся примеры, которые показывают, какой код, связанный с безопасностью, вам придется писать (за дополнительными подробностями и примерами обращайтесь к документации Oracle
).
Обычно для предоставления разрешений используется процедура DBMS_JAVA.GRANT_ PERMISSION
. В следующем примере эта программа вызывается для предоставления схеме BATCH
разрешений чтения и записи файла lastorder.log
:
/* Необходимо подключение с правами администратора базы данных */
BEGIN
DBMS_JAVA.grant_permission(
grantee => 'BATCH',
permission_type => 'java.io.FilePermission',
permission_name => '/apps/OE/lastorder.log',
permission_action => 'read,write');
END;
/
COMMIT;
Помните, что при таких вызовах получатель привилегий должен записываться в верхнем регистре; в противном случае Oracle
не найдет имя учетной записи.
Также обратите внимание на COMMIT
. Оказывается, вызов DBMS_JAVA
просто записывает данные разрешений в таблицу в словаре данных Oracle
, но эта операция не закрепляется автоматически. Для получения данных разрешений можно воспользоваться представлениями USER JAVA POLICY
и DBA JAVA POLICY
.
Следующая последовательность команд сначала предоставляет разрешение для обращения к файлам в каталоге, а затем ограничивает разрешения конкретным файлом:
BEGIN
/* Сначала разрешения на чтение и запись предоставляются всем */
DBMS_JAVA.grant_permission(
'PUBLIC',
'java.io.FilePermission',
'/shared/*',
'read,write');
/* Use the "restrict" built-in to revoke read & write
| Затем встроенная процедура отзывает разрешения
чтения и записи для конкретного файла у всех
*/
DBMS_JAVA.restrict_permission(
'PUBLIC',
'java.io.FilePermission',
'/shared/secretfile',
'read,write');
/* Теперь ограничение переопределяется так, чтобы пользователь
| мог читать и записывать этот файл
*/
DBMS_JAVA.grant_permission(
'BOB',
'java.io.FilePermission',
'/shared/secretfile',
'read,write');
COMMIT;
END;
Набор предопределенных разрешений, которые предоставляет Oracle
:
java.util.PropertyPermission
java.io.SerializablePermission
java.io.FilePermission
java.net.NetPermission
java.net.SocketPermission
java.lang.RuntimePermission
java.lang.reflect.ReflectPermission
java.security.SecurityPermission
oracle.aurora.rdbms.security.PolicyTablePermission
oracle.aurora.security.JServerPermission
Oracle
также поддерживает механизмы Java для создания ваших разрешений; за подробностями обращайтесь к руководству Oracle Java Developer’s Guide
.
Простая демонстрация
Прежде чем углубляться в подробности, рассмотрим все действия, необходимые для работы с Java
из PL/SQL
. Попутно я представлю основные технологические компоненты, необходимые для решения этой задачи.
Допустим, я хочу удалить файл из PL/SQL
. До выхода Oracle8i
Database
это делалось так:
- В
Oracle7
(7.3 и ранее) я отправлял сообщение в канал базы данных; далее программа- слушатель на C получала сообщение («Удалить файл X») и выполняла всю работу. - В
Oracle8
и выше я создавал библиотеку, которая ссылалась наDLL
-библиотеку языка C или общую библиотеку. Далее из кодаPL/SQL
вызывалась программа в этой библиотеке, которая и удаляла файл.
Решение с каналами несложно, но это неуклюжий обходной маневр. Решение с внешней процедурой в Oracle8
Database
лучше, но его тоже нельзя назвать прямолинейным, особенно если вы не знаете языка C
. Похоже, Java
-решение лучше всех остальных. Хотя оно требует некоторых базовых знаний Java
и существенно меньшей квалификации, чем для написания эквивалентного кода C
. В поставку Java
включаются готовые классы, предоставляющие компактные и удобные программные интерфейсы для разнообразных функций, включая файловый ввод/вывод.
Основные действия, выполняемые в этой демонстрации:
- Определение используемой функциональности
- Построение собственного класса для того, чтобы иметь возможность вызова функций
Java
черезPL/SQL
. - Компиляция класса и загрузка его в базу данных.
- Построение программы
PL/SQL
для вызова созданного мной метода класса. - Удаление файлов из кода
PL/SQL
.
Поиск функциональности Java
Недавно мой редактор Дебора Рассел (Deborah Russell) любезно прислала мне подборку книг по Java издательства O’Reilly. Я взял здоровенный том Java Fundamental Classes Reference (авторы Марк Гранд (Mark Grand) и Джонатан Кнудсен (Jonathan Knudsen)) и поискал в алфавитном указателе категорию «Файл». Мое внимание привлекла ссылка «класс File
», и я заглянул на указанную страницу.
Там я нашел информацию о классе с именем java.io.File
— а именно то, что он «предоставляет набор методов для получения информации о файлах и каталогах». И не только для получения информации — класс также содержит методы (процедуры и функции) для удаления и переименования файлов, создания каталогов и т. д. Именно то, что нужно! Ниже приводится часть программного интерфейса класса File
:
public class java.io.File {
public boolean delete();
public boolean mkdir ();
}
Другими словами, я вызываю логическую функцию Java
для удаления файла. Если удаление файла проходит успешно, функция возвращает TRUE
; в противном случае возвращается FALSE
.
Построение класса Java
Зачем строить собственный класс Java
на базе класса File
? Почему бы не вызвать эту функцию в обертке PL/SQL
? По двум причинам:
- Метод класса
Java
обычно выполняется для конкретного объекта — экземпляра, который создается на основе класса. ВPL/SQL
я не могу создать объектJava
, а затем снова вызвать метод для этого объекта; иначе говоря,PL/SQL
позволяет вызывать только статические методы. - Хотя и в
Java
, и вPL/SQL
существуют логические типы данных (вJava
даже существует примитивный тип и класс), они не соответствуют друг другу. Невозможно передать логическое значение изJava
напрямую как логическое значениеPL/SQL
.
Следовательно, я должен построить собственный класс, который будет:
- создавать объект класса
File
; - выполнять метод
delete
этого объекта; - возвращать значение, которое правильно интерпретируется в
PL/SQL
.
Очень простой класс, использующий метод File.delete
, выглядит так:
import java.io.File;
public class JDelete {
public static int delete (String fileName) {
File myFile = new File (fileName);
boolean retval = myFile.delete();
if (retval) return 1; else return 0;
}
}
Рисунок 2 поясняет смысл каждого шага в этом коде, но суть происходящего ясна: метод JDelete.delete
просто создает фиктивный объект File
для заданного имени файла, чтобы я мог вызвать для этого файла метод delete
. Объявляя метод статическим, я делаю возможным его использование без создания экземпляра. Статические методы связываются с классом, а не с конкретными экземплярами этого класса.
Рис. 2. Простой класс Java
, предназначенный для удаления файла
Приведенный класс JDelete
подчеркивает некоторые различия между Java
и PL/SQL
, о которых следует помнить:
- В
Java
нет ключевых словBEGIN
иEND
для блоков, циклов и условных команд. Вместо этого блок заключается в фигурные скобки. - В
Java
учитывается регистр символов: «if
» — совсем не то же самое, что «IF
». - В качестве оператора присваивания используется обычный знак равенства (
=
) вместо составного оператора, используемого вPL/SQL
(:=
). - При вызове метода, не получающего аргументов (например, метода
delete
классаFile
), все равно необходимо ставить пару круглых скобок. В противном случае компиляторJava
попытается интерпретировать метод как член класса или структуры данных.
В общем-то ничего сложного! Конечно, вы не видели, как я возился с множеством мелких синтаксических ошибок, сталкивался со скрытыми ловушками регистра символов и мучился с настройкой CLASSPATH
. Представьте сами — и не надейтесь, что в вашей работе всего этого не будет!
Компиляция и загрузка в Oracle
Написанный класс нужно откомпилировать. На компьютере с Microsoft Windows
для этого можно было бы открыть консольный сеанс из каталога, в котором хранится исходный код, убедиться в том, что путь к компилятору Java
(javac.exe
) включен в список PATH
, и выполнить следующую команду:
C:\samples\java> javac JDelete.java
Если компиляция проходит успешно, компилятор генерирует файл с именем JDelete.class
. Понятно, что функцию стоит протестировать, прежде чем загружать ее в Oracle
и пытаться использовать из PL/SQL
. Построение и тестирование всегда желательно проводить в пошаговом режиме. Java
предоставляет для этого простое средство: метод main
. Если вы включите в свой класс метод, не возвращающий значения (то есть процедуру) с именем main
, и передадите ему правильный список параметров, то вы сможете вызвать свой класс, и при вызове будет выполнен этот код.
Метод main
— один из примеров особой интерпретации в языке Java
некоторых элементов, имеющих правильную сигнатуру. Другим примером служит метод toString
. Если вы добавите в класс метод с таким именем, он будет автоматически вызываться для вывода вашего пользовательского описания объекта. Данная возможность особенно полезна для классов, состоящих из множества элементов, которые имеют смысл только в определенном представлении или же по иным причинам требуют дополнительного форматирования для восприятия пользователем.
Итак, добавим в JDelete
простой метод main
:
import java.io.File;
public class JDelete {
public static int delete (String fileName) {
File myFile = new File (fileName);
boolean retval = myFile.delete();
if (retval) return 1; else return 0;
}
public static void main (String args[]) {
System.out.println (
delete (args[0])
);
}
}
Первый элемент массива args
(0) представляет первый аргумент, переданный из среды вызова. Затем класс следует перекомпилировать:
C:\samples\java> javac JDelete.java
Если предположить, что каталог с исполняемым файлом java
включен в список PATH
, тогда
C:\samples\java> java JDelete c:\temp\te_employee.pks
1
C:\samples\java> java JDelete c:\temp\te_employee.pks
0
При первом запуске метода main выводится значение 1 (true
), которое сообщает, что файл был удален. Поэтому вполне логично, что при повторном выполнении той же команды main
выводит 0. Трудно удалить файл, который уже был удален.
Мой класс компилируется, и я проверил, что метод delete
работает — теперь можно загрузить его в схеме SCOTT
базы данных Oracle
при помощи команды Oracle
loadjava
.
И еще одно преимущество Java
перед PL/SQL
: если в PL/SQL
для вывода строки приходится вводить целых 20 символов (DBMS_OUTPUT.PUT_LINE
), в Java
достаточно всего 18 (System.out.println
). Проектировщики языков, пожалейте нас! Впрочем, Алекс Романкевич, один из наших технических рецензентов, замечает, что если включить в начало класса объявление «private static final PrintStream o = System. out;
», то данные можно будет выводить командой o.println
— всего девять символов!
Программа loadjava
включается в поставку Oracle
, поэтому путь к ней уже должен быть включен в список PATH
, если на вашей локальной машине установлен сервер или клиент Oracle
:
C:\samples\java> loadjava -user scott/tiger -oci8 -resolve JDelete.class
Я даже могу убедиться в том, что класс загружен, запросив содержимое словаря данных USER_OBJECTS
при помощи вспомогательной программы, которая будет представлена позднее в этом блоге:
SQL> EXEC myjava.showobjects
Object Name Object Type Status Timestamp
---------------------------------------------------------------------
JDelete JAVA CLASS VALID 2005-05-06:15:01
Собственно, это все, что относится исключительно к Java
. Теперь мы можем вернуться в уютный мир PL/SQL
.
Построение обертки PL/SQL
Наша задача — сделать так, чтобы каждый подключившийся к моей базе данных смог удалять файлы из PL/SQL
без лишних хлопот. Для этого я создам обертку PL/SQL
, которая внешне напоминает функцию PL/SQL
, но в действительности попросту передает управление коду Java
:
FUNCTION fDelete (
file IN VARCHAR2)
RETURN NUMBER
AS LANGUAGE JAVA
NAME 'JDelete.delete (
java.lang.String)
return int';
Реализация функции fdelete
состоит из строки, описывающей вызов метода Java
. Список параметров должен соответствовать списку параметров метода, но на месте каждого параметра указывается полностью уточненный тип данных. В данном случае это означает, что вместо простого «String
» я должен указать полное имя пакета Java
, содержащего класс String
. В секции RETURN
просто указывается int
для целочисленного значения. Поскольку int
является примитивным типом данных, а не классом, такое имя является полной спецификацией.
Небольшое отступление: я также мог написать спецификацию вызова для процедуры, вызывающей метод JDelete.main
:
PROCEDURE fDelete2 (
file IN VARCHAR2)
AS LANGUAGE JAVA
NAME 'JDelete.main(java.lang.String[])';
Метод main
отличается от других методов; хотя он получает массив объектов String
, спецификация вызова может определятся с любым количеством параметров.
Удаление файлов из PL/SQL
Итак, я компилирую функцию и готовлюсь выполнить почти что волшебный, некогда трудный (а то и невозможный) трюк:
SQL> @fdelete.sf
Function created.
SQL> EXEC DBMS_OUTPUT.PUT_LINE (fdelete('c:\temp\te_employee.pkb'))
И вот что я получаю:
ERROR at line 1:
ORA-29532: Java call terminated by uncaught Java exception: java.security.
AccessControlException: the
Permission (java.io.FilePermission c:\temp\te_employee.pkb delete) has not been
granted to BOB. The PL/SQL
to grant this is dbms_java.grant_permission( 'BOB', 'SYS:java.io.FilePermission',
'c:\temp\te_employee.pkb', 'delete' )
ORA-06512: at "BOB.FDELETE", line 1
ORA-06512: at line 1
Я забыл предоставить себе необходимые разрешения! Но взгляните на сообщение — Oracle
любезно сообщает мне не только суть проблемы, но и возможный способ ее исправления. Я прошу своего знакомого администратора выполнить команду следующего вида (небольшая вариация на тему предложения Oracle
):
CALL DBMS_JAVA.grant_permission(
'BOB',
'SYS:java.io.FilePermission',
'c:\temp\*',
'read,write,delete' );
А теперь результат выглядит так:
SQL> EXEC DBMS_OUTPUT.PUT_LINE (fdelete('c:\temp\te_employee.pkb'))
1
SQL> exec DBMS_OUTPUT.PUT_LINE (fdelete('c:\temp\te_employee.pkb'))
0
Ура, заработало!
Эта функция может стать основой для построения служебных программ. Как насчет процедуры, удаляющей все файлы, найденные в строках вложенной таблицы? Или и того лучше — как насчет процедуры, которая получает имя каталога и фильтр (например, «все файлы с именами вида *.tmp
») и удаляет из каталога все файлы, соответствующие фильтру?
Конечно, на практике мне следовало бы построить пакет и разместить в нем всю замечательную новую функциональность. Но сначала давайте повнимательнее присмотримся к тому, что я уже сделал.
Использование loadjava
Программа loadjava
работает в режиме командной строки и загружает файлы Java
в базу данных. При первом выполнении для схемы loadjava
создает несколько объектов в локальной схеме. Хотя точный список зависит от версии Oracle
, скорее всего, в табличном пространстве по умолчанию вы найдете следующее:
-
CREATE$JAVA$LOB$TABLE
— таблица с элементами кодаJava
, создаваемая в каждой схеме. Каждый новый класс, загружаемый командойloadjava
, создает одну строку в этой таблице, с сохранением байтов класса в столбцеBLOB
. -
SYS_C
... (точное имя зависит от версии) — уникальный индекс в упомянутой выше таблице. -
SYS_IL
... (точное имя зависит от версии) — индексLOB
в упомянутой выше таблице.
Кстати говоря, если вы не располагаете достаточными привилегиями или квотами для создания этих объектов в табличном пространстве по умолчанию, операция загрузки завершится неудачей.
Прежде чем выполнять загрузку, Oracle
сначала проверяет, существует ли загружаемый объект и изменяется ли он. Тем самым сводятся к минимуму лишние повторные загрузки и объявление недействительными зависимых классов1.
Затем операция загрузки вызывает команду DDL CREATE JAVA
для загрузки классов Java
из столбца BLOB
таблицы CREATE$JAVA$LOB$TABLE
в РСУБД в виде объектов схемы. Загрузка происходит только при выполнении следующих условий:
- Класс загружается в первый раз.
- Класс изменился.
- При вызове задан ключ -
force
.
Базовый синтаксис вызова:
loadjava {-user | -u} пользователь/пароль[@база_данных]
[ключ ...] filename [имя_файла ]...
Здесь имя_файла — файл с исходным кодом Java, SQL
J
, класса, .jar
, ресурса, свойств или .zip
. Например, следующая команда загружает класс JFile
в схему SCOTT
:
C:> loadjava -user scott/tiger -oci8 -resolve JFile.class
Несколько слов о loadjava
: для вывода справки используется следующий синтаксис:
loadjava {-help | -h}
В списке ключей или файлов имена могут разделяться только пробелами:
-force, -resolve, -thin // Нет
-force -resolve -thin // Да
Однако в списке пользователей или ролей имена должны разделяться только запятыми:
SCOTT, PAYROLL, BLAKE // Нет
SCOTT,PAYROLL,BLAKE // Да
Программа loadjava
поддерживает более 40 ключей командной строки; важнейшие ключи перечислены в табл. 2.
Таблица 2. Часто используемые ключи командной строки loadjava
Ключ | Описание |
-debug | Предназначается для отладки самого сценария loadjava, а не вашего кода; используется редко |
-definer | Указывает, что методы загруженных классов будут выполняться с привилегиями создателя, а не вызывающего. (По умолчанию методы выполняются с привилегиями вызывающего.) Разные создатели могут обладать разными привилегиями, а приложение может содержать много классов, поэтому программист должен проследить за тем, чтобы методы заданного класса выполнялись только с минимумом необходимых привилегий |
-encoding | Задает (или сбрасывает) ключ -encoding в таблице JAVA$OPTIONS ; значение должно быть именем стандартной схемы кодировки JDK (по умолчанию “latin1”). Компилятор использует это значение, так что кодировка загружаемых исходных файлов должна соответствовать заданной. О создании и использовании этого объекта рассказано в разделе «GET_ , SET_ и RESET_COMPILER_OPTION : чтение и запись параметров компилятора» (см. с. 973) |
-force | Обеспечивает принудительную загрузку файлов классов Java независимо от того, изменялись ли они с момента последней загрузки. Учтите, что принудительная загрузка файла класса невозможна, если до этого вы загрузили исходный файл (и наоборот) — сначала необходимо удалить ранее загруженный объект |
-grant | Предоставляет привилегию EXECUTE для загруженных классов перечисленным пользователям или ролям. (Чтобы вызывать методы класса напрямую, пользователи должны иметь привилегию EXECUTE .) Ключ действует с накоплением: новые пользователи и роли добавляются в список имеющих привилегию EXECUTE . Чтобы отозвать привилегию, либо удалите и перезагрузите объект схемы без -grant, либо воспользуйтесь командой SQL REVOKE . Чтобы предоставить привилегию для объекта схеме другого пользователя, необходимо иметь привилегию CREATE PROCEDURE WITH GRANT |
-oci8 | Приказывает loadjava взаимодействовать с базой через драйвер OCI JDBC . Этот ключ (используемый по умолчанию) и -thin являются взаимоисключающими. При вызове loadjava с клиентского компьютера без установки Oracle используйте ключ -thin |
-resolve | После того как все файлы в командной строке будут загружены и откомпилированы (в случае необходимости), разрешает все внешние ссылки в этих классах. Если ключ не указан, файлы загружаются, но не компилируются и не обрабатываются до времени выполнения. Ключ используется для компиляции и разрешения класса, который был загружен ранее. Указывать ключ -force не обязательно, потому что разрешение происходит независимо после загрузки |
-resolver | Связывает созданные объекты схемы класса со спецификацией определителя, заданной пользователем. Так как спецификация содержит пробелы, она должна быть заключена в двойные кавычки. Этот ключ и -oracleresolver (используется по умолчанию) являются взаимоисключающими |
-schema | Присваивает созданные объекты схемы Java заданной схеме. Если ключ не задан, используется схема logon . Для загрузки в схему другого пользователя необходимо иметь привилегию CREATE ANY PROCEDURE |
-synonym | Создает общедоступный синоним для загруженных классов, чтобы они были доступны за пределами схемы, в которой они загружаются. Для задания этого ключа необходимо иметь привилегию CREATE PUBLIC SYNONYM . Если задать этот ключ для исходных файлов, он также применяется к классам, полученным в результате компиляции этих исходных файлов |
-thin | Приказывает loadjava взаимодействовать с базой данных через «тонкий» (минимальный) драйвер JDBC . Этот ключ и -oci8 (используется по умолчанию) являются взаимоисключающими. При вызове loadjava с клиентского компьютера без установки Oracle используйте ключ -thin |
-verbose | Включает режим расширенного вывода, в котором выводятся сообщения о ходе выполнения. Очень полезно! |
Как вы, вероятно, понимаете, при работе с loadjava
приходится учитывать много разных нюансов — например, загрузку отдельных классов или сжатых групп элементов в файлах .zip
или .jar
. В документации Oracle
приводится более подробная информация о программе loadjava
.
Программа dropjava
Программа dropjava
по своему назначению противоположна loadjava
: она преобразует имена файлов в имена объектов схемы, а затем удаляет объекты схемы вместе со всеми ассоциированными данными. Удаление класса приводит к тому, что классы, зависящие от него прямо или косвенно, становятся недействительными. Удаление объекта исходного кода также приводит к удалению классов, созданных на его основе.
Синтаксис практически идентичен синтаксису loadjava
:
dropjava {-user | -u} пользователь/пароль[@база_данных]
[ключ ...] filename [имя_файла] ...
Здесь допустимыми ключами являются -oci8
, -encoding
и -verbose
.
Управление Java в базе данных
В этом разделе более подробно рассматриваются вопросы, связанные с хранением элементов Java
в базе данных и управлением этими элементами.
Пространство имен Java в Oracle
Oracle
хранит каждый класс Java
в базе данных как объект схемы. Имя объекта строится на основе полностью уточненного имени класса (хотя и не совпадает с ним) и включает имена всех вмещающих пакетов. Например, полное имя класса OracleSimpleChecker
выглядит так:
oracle.sqlj.checker.OracleSimpleChecker
В базе данных полное имя объекта схемы Java
будет выглядеть так:
oracle/sqlj/checker/OracleSimpleChecker
Иначе говоря, при сохранении в базе данных Oracle
точки замещаются косой чертой. Имя объекта в Oracle
, будь то имя таблицы базы данных или класса Java
, не может быть длиннее 30 символов. В Java
такого ограничения нет; в этом языке допускаются гораздо более длинные имена. Oracle
позволяет загрузить в базе данных Oracle
класс Java
с именем, содержащим до 4000 символов. Если имя элемента Java
содержит более 30 символов, база данных автоматически генерирует для этого элемента допустимый псевдоним (длиной менее 31 символа).
Вам никогда не придется использовать этот псевдоним в хранимых процедурах. Вместо этого вы можете продолжать использовать «настоящее» имя элемента Java
в своем коде. Oracle
при необходимости автоматически связывает длинное имя с псевдонимом.
Получение информации о загруженных элементах Java
Когда вы загрузите в базу данных элементы исходного кода, класса и элемента, информация об этих элементах становится доступной в нескольких представлениях словаря данных (табл. 3).
таблица 3. Информация о классах в представлениях словаря данных
Представление | Описание |
USER_OBJECTS ,ALL_OBJECTS ,DBA_OBJECTS | Информация об объектах типов JAVA SOURCE , JAVA CLASS и JAVA RESOURCE |
USER_ERRORS ,ALL_ERRORS ,DBA_ERRORS | Ошибки компиляции, обнаруженные при обработке объектов |
USER_SOURCE | Исходный код Java , если вы использовали команду CREATE JAVA SOURCE длясоздания объекта схемы Java |
Следующий запрос выводит информацию обо всех объектах, связанных с Java
, в моей схеме:
COLUMN object_name FORMAT A30
SELECT object_name, object_type, status, timestamp
FROM user_objects
WHERE (object_name NOT LIKE 'SYS_%'
AND object_name NOT LIKE 'CREATE$%'
AND object_name NOT LIKE 'JAVA$%'
AND object_name NOT LIKE 'LOADLOB%')
AND object_type LIKE 'JAVA %'
ORDER BY object_type, object_name;
Секция WHERE
отфильтровывает объекты, созданные Oracle
для управления объектами Java
. Хранимая информация может использоваться разными способами. Ниже приведен пример вывода пакета myjava
, доступного на сайте книги:
SQL> EXEC myJava.showObjects
Object Name Object Type Status Timestamp
---------------------------------------------------------------------
DeleteFile JAVA CLASS INVALID 0000-00-00:00:00
JDelete JAVA CLASS VALID 2005-05-06:10:13
book JAVA CLASS VALID 2005-05-06:10:07
DeleteFile JAVA SOURCE INVALID 2005-05-06:10:06
book JAVA SOURCE VALID 2005-05-06:10:07
Следующая команда выводит список всех элементов Java
, имена которых содержат строку «Delete
»:
SQL> EXEC myJava.showobjects ('%Delete%')
Столбец USER_OBJECTS.object_name
содержит полные имена объектов Java
в схеме, если только эти имена не содержат более 30 символов или непреобразуемые символы из набора Юникод. В обоих случаях в столбце object_name
выводится короткое имя. Для преобразования коротких имен в длинные используется функция LONGNAME
из пакета DBMS_JAVA
, который рассматривается в следующем разделе.
Пакет DBMS_JAVA
Встроенный пакет Oracle
DBMS_JAVA
предоставляет средства для изменения различных характеристик виртуальной машины Java
в базе данных.
Пакет DBMS_JAVA
содержит множество программ, многие из которых предназначены для внутреннего использования Oracle
. Однако мы можем пользоваться другими полезными программами, вызывая их в командах SQL
. В табл. 4 приведена сводка некоторых программ DBMS_JAVA
. Как упоминалось ранее в этой статье, в пакет DBMS_JAVA
также включены программы для управления безопасностью и разрешениями.
Таблица 4. Часто используемые программы DBMS_JAVA
Программа | Описание |
Функция LONGNAME | Получает полное (длинное) имя Java для заданного короткого имени Oracle |
Функция GET_COMPILER_OPTION | Ищет значение параметра в таблице параметров Java |
Процедура SET_COMPILER_OPTION | Записывает значение параметра в таблицу параметров Oracle и создает таблицу, если она не существует |
Процедура RESET_COMPILER_OPTION | Сбрасывает значение параметра в таблице параметров Oracle |
Процедура SET_OUTPUT | Перенаправляет вывод Java в текстовый буфер DBMS_OUTPUT |
Процедура EXPORT_SOURCE | Экспортирует объект исходного кода Java в Oracle LOB |
Процедура EXPORT_RESOURCE | Экспортирует объект ресурса Java в Oracle LOB |
Процедура EXPORT_CLASS | Экспортирует объект класса Java в Oracle LOB |
Эти программы более подробно рассматриваются ниже.
LONGNAME
: преобразование длинных имен Java
Длина имен классов Java
легко может превысить максимальную длину идентификатора SQL
, равную 30 символам. В таких случаях Oracle
создает для элемента кода Java
уникальное «короткое имя» и использует его для обращений, связанных с SQL
и PL/SQL
.
Используйте следующую функцию для получения полного (длинного) имени, соответствующего заданному короткому имени:
FUNCTION DBMS_JAVA.LONGNAME (shortname VARCHAR2) RETURN VARCHAR2
Следующий запрос выводит длинные имена всех классов Java
, определенных в текущей схеме, у которых длинные имена не совпадают с короткими:
SELECT object_name shortname,
DBMS_JAVA.LONGNAME (object_name) longname
FROM USER_OBJECTS
WHERE object_type = 'JAVA CLASS'
AND object_name != DBMS_JAVA.LONGNAME (object_name);
Этот запрос также включен в пакет myJava
(файл myJava.pkg
). Предположим, я определяю класс со следующим именем:
public class DropAnyObjectIdentifiedByTypeAndName {
Для Oracle
это имя получается слишком длинным. Я могу убедиться в том, что Oracle
создает свое короткое имя:
SQL> EXEC myJava.showlongnames
Short Name | Long Name
----------------------------------------------------
Short: /247421b0_DropAnyObjectIdentif
Long: DropAnyObjectIdentifiedByTypeAndName
GET_, SET_ и RESET_COMPILER_OPTION: чтение и запись параметров компилятора
Вы можете задать значения некоторых параметров компилятора в таблице базы данных JAVA$OPTIONS
(далее именуемой таблицей параметров). Хотя поддерживается более 40 параметров командной строки, только три из них могут сохраняться в таблице параметров:
-
encoding
— кодировка, в которой записан исходный код. Если значение не задано, компилятор использует значение по умолчанию, которое возвращается методомJava System.getProperty
("file.encoding
"), — например,ISO646-US
. -
online
—true
илиfalse
; значение применимо только к исходному кодуSQLJ
.
Значение по умолчанию true
обеспечивает проверку online-семантики.
-
debug
—true
илиfalse
; значение true эквивалентно использованиюjavac -g
. Если параметр не задан, компилятор по умолчанию использует значениеtrue
.
Если значения параметров не заданы в командной строке loadjava, компилятор ищет их в таблице параметров.
Значения этих трех параметров могут задаваться следующими функциями и процедурами dbms_java
:
FUNCTION DBMS_JAVA.GET_COMPILER_OPTION (
объект VARCHAR2, имя_параметра VARCHAR2)
PROCEDURE DBMS_JAVA.SET_COMPILER_OPTION (
объект VARCHAR2, имя_параметра VARCHAR2, значение VARCHAR2)
PROCEDURE DBMS_JAVA.RESET_COMPILER_OPTION (
объект VARCHAR2, имя_параметра VARCHAR2)
Здесь объект — имя пакета Java
, полное имя класса или пустая строка. После поиска в таблице параметров компилятор выбирает строку, в которой значение объект ближе всего соответствует полному имени объекта схемы. Если объект — пустая строка, она совпадает с именем любого объекта схемы. Параметр имя параметра определяет имя задаваемого параметра. Изначально в схеме не существует таблицы параметров. Чтобы создать ее, используйте процедуру DBMS_JAVA.SET_COMPlLER_OPTlON
для задания значения. Если таблица не существует, процедура создает ее. Параметры заключаются в апострофы, как показано в следующем примере:
16:47:17
SQL> DBMS_JAVA.SET_COMPILER_OPTION ('X.sqlj', 'online', 'false');
SET_OUTPUT
: включение вывода из Java
При выполнении из базы данных Oracle
классы System. out
и System.err
передают свой вывод в текущие файлы трассировки, обычно находящиеся в серверном подкаталоге udump
. Это не самое удобное место, если вы просто хотите протестировать свой код и убедиться в правильности его работы. Пакет DBMS_JAVA
предоставляет процедуру, вызов которой перенаправляет вывод в текстовый буфер DBMS_OUTPUT
для автоматического вывода на экран SQL*Plus
. Процедура имеет следующий синтаксис:
PROCEDURE DBMS_JAVA.SET_OUTPUT (buffersize NUMBER);
Пример использования этой программы:
SET SERVEROUTPUT ON SIZE 1000000
CALL DBMS_JAVA.SET_OUTPUT (1000000);
Передача любого целого числа DBMS_JAVA.set_output
приводит к его включению. Документации по взаимодействию между этими двумя командами явно недостаточно, но мое тестирование продемонстрировало следующее поведение:
- Минимальный размер буфера (используемый по умолчанию) составляет всего 2000 байт; максимальный размер равен 1 000 000 байт, по крайней мере в
Oracle
Database
10g Release 1
. Передача значения за пределами указанного диапазона не вызывает ошибки; максимум автоматически устанавливается равным 1 000 000 (кроме очень больших чисел). - Размер буфера, заданный
SET SERVEROUTPUT
, заменяет размер, заданныйDBMS_JAVA
.SET_OUTPUT
. Другими словами, если указать меньшее значение при вызовеDBMS_JAVA
, оно будет проигнорировано, и в программе будет использован больший размер. - Если вы используете
Oracle Database 10g Release 2
и выше и размерSERVEROUTPUT
задан неограниченным (UNLIMITED
), то максимальный размер буфераJava
тоже неограничен. - Если вывод в
Java
превышает размер буфера, вы не получите ошибку, которую выдаетdbms_output
, а именно:
ORA-10027: buffer overflow, limit of nnn bytes
Вместо этого вывод усекается по заданному размеру буфера, а выполнение кода продолжается.
Как и в случае с DBMS_OUTPUT
, вы не увидите вывода вызовов Java
до завершения хранимой процедуры, в которой они выполняются.
EXPORT_SOURCE, EXPORT_RESOURCE и EXPORT_CLASS: экспортирование объектов схемы
Пакет Oracle DBMS_JAVA
предоставляет набор процедур для экспортирования исходного кода, ресурсов и классов:
PROCEDURE DBMS_JAVA.EXPORT_SOURCE (
name VARCHAR2 IN,
[ blob BLOB IN | clob CLOB IN ]
);
PROCEDURE DBMS_JAVA.EXPORT_SOURCE (
name VARCHAR2 IN,
schema VARCHAR2 IN,
[ blob BLOB IN | clob CLOB IN ]
);
PROCEDURE DBMS_JAVA.EXPORT_RESOURCE (
name VARCHAR2 IN,
[ blob BLOB IN | clob CLOB IN ]
);
PROCEDURE DBMS_JAVA.EXPORT_RESOURCE (
name VARCHAR2 IN,
schema VARCHAR2 IN,
[ blob BLOB IN | clob CLOB IN ]
);
PROCEDURE DBMS_JAVA.EXPORT_CLASS (
name VARCHAR2 IN,
blob BLOB IN
);
PROCEDURE DBMS_JAVA.EXPORT_CLASS (
name VARCHAR2 IN,
schema VARCHAR2 IN,
blob BLOB IN
);
Во всех случаях name определяет имя экспортируемого объекта схемы Java
, схема — имя схемы, которой принадлежит объект (если схема не задана, используется текущая схема), а blob
| clob
— большой объект, получающий заданный объект схемы Java
. Классы не могут экспортироваться в CLOB
, только в BLOB
. Кроме того, во внутреннем представлении исходного кода используется формат UTF-8
, поэтому этот формат также используется для хранения исходного кода в BLOB
.
Следующий прототип процедуры дает представление о том, как экспортирование используется для получения исходного кода объектов схемы Java
:
PROCEDURE show_java_source (
NAME IN VARCHAR2, SCHEMA IN VARCHAR2 := NULL
)
-- Общие сведения: вывод исходного кода Java (прототип). Автор: Вадим Лоевский.
IS
b CLOB;
v VARCHAR2 (2000);
i INTEGER;
object_not_available EXCEPTION;
PRAGMA EXCEPTION_INIT(object_not_available, −29532);
BEGIN
/* Перемещение исходного кода Java в CLOB. */
DBMS_LOB.createtemporary(b, FALSE );
DBMS_JAVA.export_source(name, NVL(SCHEMA, USER), b);
/* Чтение CLOB в переменную VARCHAR2 и ее вывод. */
i := 1000;
DBMS_lob.read(b, i, 1, v);
DBMS_OUTPUT.put_line(v);
EXCEPTION
/* Если объект с указанным именем не существует, происходит исключение. */
WHEN object_not_available
THEN
IF DBMS_UTILITY.FORMAT_ERROR_STACK LIKE '%no such%object'
THEN
DBMS_OUTPUT.put_line ('Java object cannot be found.' );
END IF;
END;
Допустим, после этого я создаю объект исходного кода Java
командой CREATE JAVA
:
CREATE OR REPLACE JAVA SOURCE NAMED "Hello"
AS
public class Hello {
public static String hello() {
return "Hello Oracle World";
}
};
Исходный код просматривается следующей командой (предполагается, что вывод dbms_output
включен):
SQL> EXEC show_java_source ('Hello')
public class Hello {
public static String hello() {
return "Hello Oracle World";
}
};
Публикация и использование кода Java
в PL/SQL
После того как вы напишете свои классы Java
и загрузите их в базу данных Oracle
, их методы можно будет вызывать из PL/SQL
(и SQL
) — но только после того, как методы будут «опубликованы» при помощи обертки PL/SQL
.
спецификация вызова
Обертки PL/SQL
необходимо построить только для тех методов Java
, которые должны быть доступны через интерфейс PL/SQL
. Методы Java
могут вызывать другие методы Java
в виртуальной машине Java
напрямую, без оберток. Чтобы опубликовать метод Java
, вы пишете спецификацию вызова — заголовок программы PL/SQL
(функции или процедуры), тело которой в действительности представляет собой вызов метода Java
с использованием секции LANGUAGE JAVA
. Эта секция включает следующую информацию о методе Java
: полное имя, типы параметров и возвращаемый тип. Спецификации вызовов могут определяться как отдельные функции или процедуры, как программы в пакетах или методы объектного типа, с использованием синтаксиса AS LANGUAGE JAVA
после заголовка программной единицы:
{IS | AS} LANGUAGE JAVA
NAME 'полное_имя_метода (тип_java[, тип_java]...)
[return тип_java]';
Здесь munjava — либо полное имя типа Java
(например, java.lang.String
), либо примитивный тип (например, int
). Обратите внимание: спецификация не должна включать имена параметров, только типы.
Секция NAME
однозначно идентифицирует инкапсулируемый метод Java
. Полное имя Java
и параметры спецификации вызова, определяемые по позиции, должны соответствовать параметрам программы. Если метод Java
вызывается без аргументов, укажите пустой список параметров.
Несколько примеров:
- Отдельная функция, вызывающая метод (см. выше):
FUNCTION fDelete (
file IN VARCHAR2)
RETURN NUMBER
AS LANGUAGE JAVA
NAME 'JDelete.delete (
java.lang.String)
return int';
- Пакетная процедура, передающая объектный тип в параметре:
PACKAGE nat_health_care
IS
PROCEDURE consolidate_insurer (ins Insurer)
AS LANGUAGE JAVA
NAME 'NHC_consolidation.process(oracle.sql.STRUCT)';
END nat_health_care;
- Метод объектного типа:
TYPE pet_t AS OBJECT (
name VARCHAR2(100),
MEMBER FUNCTION date_of_birth (
name_in IN VARCHAR2) RETURN DATE
AS LANGUAGE JAVA
NAME 'petInfo.dob (java.lang.String)
return java.sql.Timestamp'
);
- Отдельная процедура с параметром
OUT
:
PROCEDURE read_out_file (
file_name IN VARCHAR2,
file_line OUT VARCHAR2
)
AS
LANGUAGE JAVA
NAME 'utils.ReadFile.read(java.lang.String
,java.lang.String[])';
Правила спецификаций вызовов
Обратите внимание на следующие обстоятельства:
- Спецификация вызова
PL/SQL
и публикуемый ею методJava
должны находиться в одной схеме. - Спецификация вызова предоставляет
Oracle
точку входа верхнего уровня. Соответственно публиковать можно только открытые статические методы, если только вы не определяете метод объектного типаSQL
. В этом случае методы экземпляра могут публиковаться как методы-члены этого типа. - В списке параметров программы
PL/SQL
, служащей оберткой для вызова методаJava
, не могут задаваться значения по умолчанию. - Методы в объектно-ориентированных языках не могут присваивать значения объектам, передаваемым в аргументах; методы предназначены для применения к объекту, с которым они связаны. Когда вы вызываете метод из
SQL
илиPL/SQL
и изменяете значение аргумента, он должен быть объявлен как параметрOUT
илиIN OUT
в спецификации вызова. Соответствующий параметрJava
должен быть одноэлементным массивом соответствующего типа.
Значение элемента можно заменить другим объектом Java
соответствующего типа, или (только для параметров IN OUT
) изменить значение, если это допускает тип Java
. В любом случае новое значение передается на сторону вызова. Например, параметр OUT
типа NUMBER
в спецификации вызова можно связать с параметром Java
, объявленным как float
[] p, а затем присвоить новое значение p[0].
Функция, объявляющая параметры OUT
или IN OUT
, не может вызываться из DML
-команд SQL
.
Отображение типов данных
Ранее в этой статье был приведен очень простой пример обертки PL/SQL
— функция удаления файлов, которая передавала значение VARCHAR2
для параметра java.lang
. String
. Метод Java
возвращал значение int, которое затем возвращалось секцией RETURN NUMBER
функции PL/SQL
. Это тривиальные примеры отображения типов данных, то есть установления соответствия между типом данных PL/SQL
и типом данных Java
. При построении спецификации вызова PL/SQL
параметры PL/SQL
и Java
, а также результат функции сопоставляются по позиции и должны иметь совместимые типы данных. В табл. 5 перечислены все отображения типов данных, допустимые в настоящее время между PL/SQL
и Java
. Если вы используете поддерживаемое отображение типов данных, Oracle
выполнит преобразование автоматически.
Таблица 5. Допустимые отображения типов данных
Тип SQL | Класс Java | Тип SQL | Класс Java |
CHAR | oracle.sql.CHAR | BFILE | oracle.sql.BFILE |
NCHAR | java.lang.String | BLOB | oracle.sql.BLOB oracle.jdbc2.Blob |
LONG | ava.sql.Date | CLOB | oracle.sql.CLOB |
VARCHAR2 | java.sql.Time | NCLOB | oracle.jdbc2.Clob |
NVARCHAR2 | java.sql.Timestamp java.lang.Byte java.lang.Short java.lang.Integer java.lang.Long java.lang.Float java.lang.Double java.math.BigDecimal byte, short, int, long, float, double | Пользовательский объектный тип | oracle.sql.STRUCT java.sql.Struct java.sql.SqlData oracle.sql.ORAData |
DATE | oracle.sql.DATE java.sql.Date java.sql.Time java.sql.Timestamp java.lang.String | Пользовательский тип REF | oracle.sql.REF java.sql.Ref oracle.sql.ORAData |
NUMBER | oracle.sql.NUMBER java.lang.Byte java.lang.Short java.lang.Integer java.lang.Long java.lang.Float java.lang.Double java.math.BigDecimal byte, short, int, long, float, double | Непрозрачный тип (такой, как XMLType) | oracle.sql.OPAQUE |
RAW | oracle.sql.RAW | TABLE VARRAY | oracle.sql.ARRAY |
LONG RAW | byte[] | Пользовательский тип таблицы или VARRAY | java.sql.Array oracle.sql.ORAData |
ROWID | oracle.sql.CHAR oracle.sql.ROWID java.lang.String | Любой из перечисленных типов SQL types | oracle.sql.CustomDatum oracle.sql.Datum |
Как видите, Oracle
поддерживает автоматическое преобразование только для типов данных SQL
. Такие типы данных PL/SQL
, как BINARY_INTEGER
, PLS_INTEGER
, BOOLEAN
и типы ассоциативных массивов, не поддерживаются. В таких случаях действия для передачи данных между двумя средами выполнения приходится выполнять вручную. Нестандартные отображения продемонстрированы далее в разделе «Другие примеры»; за более подробными примерами с использованием JDBC
обращайтесь к документации Oracle
.
Вызов метода Java в SQL
В DML
-командах SQL
вы можете вызывать написанные вами функции PL/SQL
. Также из кода SQL
могут вызываться методы Java
, «упакованные» в PL/SQL
. Однако такие вызовы должны подчиняться определенным правилам:
- Если метод вызывается из команды
SELECT
или из параллелизированной командыINSERT
,UPDATE
,MERGE
илиDELETE
, то он не должен изменять никакие таблицы базы данных. - Если метод вызывается из команды
INSERT
,UPDATE
,MERGE
илиDELETE
, то он не может обращаться с запросами или изменять никакие таблицы базы данных, измененные этой командой. - Если метод вызывается из команды
SELECT
,INSERT
,UPDATE
,MERGE
илиDELETE
, то он не может выполнять команды управления транзакциямиSQL
(например,COMMIT
), команды управления сеансом (например,SET
ROLE
) или команды управления системой (например,ALTER SYSTEM
). Метод также не может выполнять командыDDL
, потому что они автоматически закрепляют операции в сеансе. Учтите, что эти ограничения снимаются, если метод выполняется из блока анонимной транзакцииPL/SQL
.
Эти ограничения нужны для того, чтобы избежать побочных эффектов, способных нарушить работу команд SQL
. Если вы пытаетесь выполнить команду SQL
, которая вызывает метод, нарушающий какие-либо из этих правил, при разборе команды SQL
произойдет ошибка времени выполнения.
Обработка исключений в Java
С другой стороны, архитектура обработки исключений Java очень близка к PL/SQL
, хотя механизм обработки исключений намного мощнее. Java
предоставляет базовый класс исключения с именем Exception
. Все исключения представляют собой объекты, созданные на базе этого класса — или классов, производных от него (расширяющих этот класс). Вы можете передавать исключения в параметрах и работать с ними практически так же, как с объектами любого другого класса.
Когда хранимый метод Java
выполняет команду SQL
и при этом происходит исключение, оно представляется объектом субкласса java.sql.SQLException
. Этот субкласс содержит два метода, которые возвращают код ошибки Oracle
и сообщение об ошибке: getErrorCode
и getMessage
.
Если хранимая процедура Java, вызванная из SQL
или PL/SQL
, инициирует исключение, которое не перехватывается виртуальной машиной, вызывающая сторона получает инициированное исключение с сообщением об ошибке Java. Так передается информация обо всех не перехваченных исключениях (в том числе не относящихся к SQL
). Рассмотрим разные способы обработки ошибок и информации, которая при этом выводится. Допустим, я создаю класс, который использует JDBC
для удаления объектов в базе данных (код взят из примера в документации Oracle
):
import java.sql.*;
import java.io.*;
import oracle.jdbc.driver.*;
public class DropAny {
public static void object (String object_type, String object_name)
throws SQLException {
// Подключение к Oracle с использованием драйвера JDBC
Connection conn = new OracleDriver().defaultConnection();
// Построение команды SQL
String sql = "DROP " + object_type + " " + object_name;
Statement stmt = conn.createStatement();
try {
stmt.executeUpdate(sql);
}
catch (SQLException e) {
System.err.println(e.getMessage());
}
finally {
stmt.close();
}
}
}
Этот фрагмент перехватывает и выводит все исключения SQLException
в выделенном коде. Секция finally
гарантирует, что метод close
будет выполнен независимо от того, произошло исключение или нет (это необходимо для правильной обработки открытого курсора).
Хотя использование JDBC
для удаления объектов не имеет особого смысла, потому что это гораздо проще делается в динамическом SQL
, реализация на Java
позволяет пользоваться этой функциональностью из других Java
-программ без обращения к PL/SQL
.
Класс загружается в базу данных командой loadjava
, после чего упаковывается в процедуре PL/SQL
следующим образом:
PROCEDURE dropany (
tp IN VARCHAR2,
nm IN VARCHAR2
)
AS LANGUAGE JAVA
NAME 'DropAny.object (
java.lang.String,
java.lang.String)';
При попытке удаления несуществующего объекта возможен один из двух результатов:
SQL> CONNECT scott/tiger
Connected.
SQL> SET SERVEROUTPUT ON
SQL> BEGIN dropany ('TABLE', 'blip'); END;/
PL/SQL procedure successfully completed.
SQL> CALL DBMS_JAVA.SET_OUTPUT (1000000);
Call completed.
SQL> BEGIN dropany ('TABLE', 'blip'); END;/
ORA-00942: table or view does not exist
Эти примеры лишний раз напомнят вам, что вывод System.err.println
не появится на экране до тех пор, пока вы не включите его явно вызовом DBMS_JAVA.SET_OUTPUT
. Впрочем, в любом случае в вызывающий блок никакое исключение не передается, потому что оно перехватывается в Java
. После второго вызова dropany
мы видим, что сообщение об ошибке, переданное методом getMessage
, берется прямо из Oracle
.
Если закомментировать обработчик исключения в методе DropAny.object
, результат будет выглядеть примерно так (режим SERVEROUTPUT
включен — как и вывод Java
):
SQL > BEGIN
2 dropany('TABLE', 'blip');
3 EXCEPTION
4 WHEN OTHERS
5 THEN
6 DBMS_OUTPUT.PUT_LINE(SQLCODE);
7 DBMS_OUTPUT.PUT_LINE(SQLERRM);
8 END;
9 /
oracle.jdbc.driver.OracleSQLException: ORA-00942: table or view does not exist
at oracle.jdbc.driver.T2SConnection.check_error(T2SConnection.java:120)
at oracle.jdbc.driver.T2SStatement.check_error(T2SStatement.java:57)
at oracle.jdbc.driver.T2SStatement.execute_for_rows(T2SStatement.java:486)
at oracle.jdbc.driver.OracleStatement.doExecute
WithTimeout(OracleStatement.java:1148)
at oracle.jdbc.driver.OracleStatement.executeUpdate(OracleStatement.java:1705)
at DropAny.object(DropAny:14)
−29532
ORA-29532: Java call terminated by uncaught Java exception: java.sql.SQLException:
ORA-00942: table or view does not exist
Пожалуй, это стоит пояснить. Все, что находится между строками
java.sql.SQLException: ORA-00942: table or view does not exist
и
−29532
представляет дамп стека ошибок; Java
генерирует его и отправляет в стандартный вывод независимо от того, как ошибка обрабатывается в PL/SQL
. Другими словами, даже если мой раздел исключений будет иметь следующий вид:
EXCEPTION WHEN OTHERS THEN NULL;
эти данные все равно будут выданы на экран, после чего продолжится выполнение внешнего блока (если он есть). Последние три строки генерируются вызовами DBMS_OUTPUT.PUT_LINE
.
Обратите внимание: вместо кода ошибки Oracle
ORA-00942
выводится код ORA-29532
, обобщенная ошибка Java
. Это создает проблемы — если вы перехватите ошибку, как узнать, что произошло?
Насколько мне удалось выяснить, ошибка возвращается SQLERRM
в формате
ORA-29532: Java call ...: java.sql.SQLException: ORA-NNNNN ...
Следовательно, я могу поискать вхождение java.sql.SQLException
и выделить подстроку вызовом SUBSTR
с этой позиции.
На сайте книги имеется файл getErrorInfo.sp
с программой, которая возвращает код и сообщение для текущей ошибки. Дополнительная информация компенсирует недостатки формата сообщения об ошибке Java
.
В следующих разделах мы расширим класс JDelete
до класса JFile
, существенно увеличивающего возможности работы с файлами из PL/SQL
. После этого вы узнаете, как писать классы Java
и программы PL/SQL
на базе этих классов для работы с объектами Oracle
.
Расширение функциональности файлового ввода/вывода
Пакет Oracle UTL_FILE
замечателен скорее своими недостатками, нежели достоинствами. Он поддерживает последовательное чтение и запись содержимого файлов... и всё. По крайней мере до выхода Oracle9i Database Release
2 пользователь не мог удалять файлы, изменять привилегии, копировать файлы, читать содержимое каталогов, задавать пути и т. д.
Java
приходит на помощь! В Java
существует много разных классов для работы с файлами. Вы уже видели класс File
и знаете, как легко с его помощью добавить в PL/SQL
поддержку удаления файлов.
Сейчас мы создадим новый класс JFile
, с помощью которого разработчик PL/SQL
сможет получить ответы на вопросы и выполнить операции из следующего списка:
- Возможно ли чтение из файла? Запись в файл? Существует ли файл? Является ли заданный объект файлом или каталогом?
- Каков размер файла в байтах? В каком каталоге находится файл?
- Как получить имена всех файлов в каталоге, соответствующих заданному фильтру?
- Как создать каталог? Переименовать файл? Изменить расширение файла?
Я не стану подробно описывать все методы класса JFile
и соответствующий пакет; в них много повторяющихся фрагментов, а большинство методов Java
почти не отличается от функции удаления, построенной в начале этого блога. Вместо этого я сосредоточусь на специфических особенностях разных частей класса и пакета. Полное определение кода содержится в следующих файлах на сайте книги:
JFile.java
— классJava
, который объединяет разные виды информации о файлах операционной системы и предоставляет доступ к ней через программный интерфейс, доступный изPL/SQL
.xfile.pkg
— пакетPL/SQL
, обертка для классаJFile
.
В Oracle9i Database Release
2 появилась расширенная версия пакета UTL_FILE
, которая, среди прочего, позволяет удалять файлы процедурой UTL_FILE.FREMOVE
. Также поддерживаются операции копирования (FCOPY
) и переименования (FRENAME
) файлов.
Доработка метода удаления
Прежде чем переходить к новым интересным вопросам, следует убедиться в том, что сделанное ранее работает идеально. Мое определение метода JDelete.delete
и функции delete_file
далеко от идеала. Код метода, который уже приводился ранее, выглядит так:
public static int delete (String fileName) {
File myFile = new File (fileName);
boolean retval = myFile.delete();
if (retval) return 1; else return 0;
}
Связанный с ним код PL/SQL
:
FUNCTION fDelete (
file IN VARCHAR2) RETURN NUMBER
AS LANGUAGE JAVA
NAME 'JDelete.delete (java.lang.String)
return int';
В чем проблема? В том, что я был вынужден использовать неудобные числовые представления для TRUE/FALSE
. В результате мне пришлось писать код следующего вида:
IF fdelete ('c:\temp\temp.sql') = 1 THEN ...
Получается уродливый код с жестко запрограммированными значениями. Кроме того, любой разработчик, пишущий код PL/SQL
, должен будет знать о значениях TRUE
и FALSE
, встроенных в класс Java
.
Функция delete_file
гораздо лучше смотрелась бы с заголовком следующего вида:
FUNCTION fDelete (
file IN VARCHAR2) RETURN BOOLEAN;
Итак, давайте посмотрим, что нужно для того, чтобы предоставить пользователям пакета xfile
этот простой и удобный интерфейс.
Сначала класс JDelete
будет переименован в JFile
, чтобы в имени отражалось расширение его функциональности. Затем я добавлю методы, инкапсулирующие значения TRUE/FALSE
, возвращаемые другими методами, — и вызову их из метода delete
. Результат выглядит так:
import java.io.File;
public class JFile {
public static int tVal () { return 1; };
public static int fVal () { return 0; };
public static int delete (String fileName) {
File myFile = new File (fileName);
boolean retval = myFile.delete();
if (retval) return tVal();
else return fVal();
}
}
Проблемы на стороне Java
решены; переходим к пакету PL/SQL
. Первая версия спецификации xfile
:
PACKAGE xfile
IS
FUNCTION delete (file IN VARCHAR2)
RETURN BOOLEAN;
END xfile;
Как реализовать функцию, возвращающую логическое значение? Передо мной стоят две задачи:
- Скрыть тот факт, что для передачи
TRUE
илиFALSE
используются числовые значения. - Избежать жесткого кодирования значений 1 и 0 в пакете.
Для этого я определяю в своем пакете две глобальные переменные для хранения числовых значений:
PACKAGE BODY xfile
IS
g_true INTEGER;
g_false INTEGER;
В конце тела пакета создается инициализационный раздел, который вызывает эти программы для инициализации глобальных переменных. Выполняя этот шаг в разделе инициализации, я избегаю лишних вызовов методов Java
(и связанных с ними затрат):
BEGIN
g_true := tval;
g_false := fval;
END xfile;
В разделе объявлений тела пакета определяются две приватные функции; их единственной целью является доступ из кода PL/SQL
к методам JFile
, инкапсулирующим значения 1 и 0:
FUNCTION tval RETURN NUMBER
AS LANGUAGE JAVA
NAME 'JFile.tVal () return int';
FUNCTION fval RETURN NUMBER
AS LANGUAGE JAVA
NAME 'JFile.fVal () return int';
Чтобы в спецификации пакета можно было использовать функцию с логическим возвращаемым значением, я создаю приватную функцию «внутреннего удаления», которая представляет собой обертку для метода JFile.delete
. Функция возвращает число:
FUNCTION Idelete (file IN VARCHAR2) RETURN NUMBER
AS LANGUAGE JAVA
NAME 'JFile.delete (java.lang.String) return int';
Наконец, моя открытая функция delete
теперь может вызвать Idelete
и преобразовать целое число в логическое значение, проверяя его по глобальной переменной:
FUNCTION delete (file IN VARCHAR2) RETURN BOOLEAN
AS
BEGIN
RETURN Idelete (file) = g_true;
EXCEPTION
WHEN OTHERS
THEN
RETURN FALSE;
END;
Так логическое значение Java
преобразуется в логическое значение PL/SQL
. Этот прием неоднократно применяется в теле пакета xfile
.
Чтение содержимого каталога
Одна из самых интересных возможностей JFile
— получение списка файлов в каталоге. Она реализуется вызовом метода File.list
; если строка, которая использовалась для построения нового объекта File
, содержит имя каталога, то метод возвращает массив строк с именами файлов, находящихся в этом каталоге. Давайте посмотрим, как предоставить доступ к этой информации в виде коллекции PL/SQL
.
Начнем с создания типа коллекции:
CREATE OR REPLACE TYPE dirlist_t AS TABLE OF VARCHAR2(512);
Затем создается метод dirlist
, который возвращает oracle.sql.ARRAY
:
/* File on web: JFile.java */
import java.io.File;
import java.sql.*;
import oracle.sql.*;
import oracle.jdbc.*;
public class JFile {
...
public static oracle.sql.ARRAY dirlist (String dir)
throws java.sql.SQLException
{
Connection conn = new OracleDriver().defaultConnection();
ArrayDescriptor arraydesc =
ArrayDescriptor.createDescriptor ("DIRLIST_T", conn);
File myDir = new File (dir);
String[] filesList = myDir.list();
ARRAY dirArray = new ARRAY(arraydesc, conn, filesList);
return dirArray;
}
...
Метод сначала получает «дескриптор» пользовательского типа dirlist_t
для создания соответствующего объекта. После вызова метода Java
File.list
он копирует полученный список файлов в объект ARRAY
при вызове конструктора.
На стороне PL/SQL
создается обертка, которая вызывает этот метод:
FUNCTION dirlist (dir IN VARCHAR2)
RETURN dirlist_t
AS
LANGUAGE JAVA
NAME 'myFile.dirlist(java.lang.String) return oracle.sql.ARRAY';
Простой пример возможного вызова:
DECLARE
tempdir dirlist_t;
BEGIN
tempdir := dirlist('C:\temp');
FOR indx IN 1..tempdir.COUNT
LOOP
DBMS_OUTPUT.PUT_LINE_(tempdir(indx));
END LOOP;
END;
В пакет xfile
входят дополнительные программы для выполнения других операций: получения имен файлов в виде списка (вместо массива), ограничения списка файлов по заданному фильтру и изменения расширения заданных файлов. Вы найдете в нем все точки входа пакета UTL_FILE
, такие как FOPEN
и PUT_LINE
. Я добавил их, чтобы вы могли избежать использования UTL_FILE
для любых целей, кроме объявлений файловых дескрипторов в виде UTL_FILE.FILE_TYPE
и ссылок на исключения, объявленных в UTL_FILE
.
Другие примеры
На сайте книги имеются другие интересные примеры применения Java
для расширения функциональности PL/SQL
или выполнения более сложного отображения типов данных:
-
utlzip.sql
— классJava
и соответствующий пакет для использования функциональности zip/сжатия вPL/SQL
(предоставлен рецензентом Вадимом Лоевским). Также можно воспользоваться командойCREATE OR REPLACE JAVA
для прямой загрузки класса в базу данных без командыloadjava
. Заголовок команды создания класса Java:
JAVA SOURCE NAMED "UTLZip" AS
import java.util.zip.*;
import java.io.*;
public class utlzip
{ public static void compressfile(string infilename, string outfilename)
...
}
А вот обертка для метода Java:
PACKAGE utlzip
IS
PROCEDURE compressfile (p_in_file IN VARCHAR2, p_out_file IN VARCHAR2)
AS
LANGUAGE JAVA
NAME 'UTLZip.compressFile(java.lang.String,
java.lang.String)';
END;
В Oracle Database 10g
и выше вы можете просто воспользоваться встроенным пакетом Oracle
utl_compress
.
-
DeleteFile.java
иdeletefile.sql
— классJava
и кодPL/SQL
, демонстрирующий передачу коллекций (вложенных таблиц илиVARRAY
) в массивыJava
(предоставлен рецензентом Алексом Романкевичем). Код реализует удаление всех файлов в заданном каталоге, измененных после заданной даты. В реализации на сторонеPL/SQL
я сначала создаю вложенную таблицу объектов, а затем передаю ееJava
при помощи классаoracle.sql.ARRAY
:
CREATE TYPE file_details AS OBJECT (
dirname VARCHAR2 (30),
deletedate DATE)
/
CREATE TYPE file_table AS TABLE OF file_details;
/
CREATE OR REPLACE PACKAGE delete_files
IS
FUNCTION fdelete (tbl IN file_table) RETURN NUMBER
AS
LANGUAGE JAVA
NAME 'DeleteFile.delete(oracle.sql.ARRAY) return int';
END delete_files;
Ниже приводится начало метода Java
(за полной реализацией и подробными комментариями обращайтесь к сценарию DeleteFile.java
). Алекс извлекает результирующий набор из структуры массива, после чего перебирает элементы этого набора:
/* Файлы в Сети: DeleteFile.java and deletefile.sql */
public class DeleteFile {
public static int delete(oracle.sql.ARRAY tbl) throws SQLException {
try {
// Получение содержимого таблицы/varray как результирующего набора
ResultSet rs = tbl.getResultSet();
for (int ndx = 0; ndx < tbl.length(); ndx++) {
rs.next();
// Получение индекса и элемента массива
int aryndx = (int)rs.getInt(1);
STRUCT obj = (STRUCT)rs.getObject(2);
-
utlcmd.sql
— классJava
и пакет, при помощи которых можно легко (и даже слишком!) выполнить любую команду операционной системы изPL/SQL
. Будьте осторожны!