Вызов Java-программ из PL/SQL: процедуры, loadjava, JSP, DBMS_JAVA и др.

Вызов Java-программ из PL/SQL: процедуры, loadjava, JSP, DBMS_JAVA и др.

Язык 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 проходит следующим образом:

  1. Разработчик пишет исходный код Java. При этом можно использовать любой удобный текстовый редактор или интегрированную среду разработки (например, Oracle JDeveloper).
  2. Откомпилируйте классы Java; при желании упакуйте их в файлы .jar. Эта опе­рация также может выполняться интегрированной средой или компилятором командной строки Sun javac. (Строго говоря, этот шаг не обязателен, потому что вы можете загрузить исходный код в Oracle и воспользоваться встроенным ком­пилятором Java.)
  3. Загрузите классы Java в Oracle программой командной строки loadjava или командой CREATE JAVA.
  4. Опубликуйте методы класса Java; для этого напишите «обертки» PL/SQL для вызова кода Java.
  5. Предоставьте необходимые привилегии для обертки PL/SQL.
  6. Вызовите программы PL/SQL в одной из доступных сред (рис. 1).

 

Обращение к JSP из базы данных Oracle

Рис. 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 включаются готовые классы, предоставляющие компактные и удобные программные интерфейсы для разнообразных функций, включая файловый ввод/вывод.

Основные действия, выполняемые в этой демонстрации:

  1. Определение используемой функциональности
  2. Построение собственного класса для того, чтобы иметь возможность вызова функций Java через PL/SQL.
  3. Компиляция класса и загрузка его в базу данных.
  4. Построение программы PL/SQL для вызова созданного мной метода класса.
  5. Удаление файлов из кода 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. Объявляя метод статическим, я делаю возможным его использование без создания экземпляра. Статические методы связываются с классом, а не с конкретными экземплярами этого класса.

 

Простой класс Java, предназначенный для удаления файла

Рис. 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.
  •  onlinetrue или false; значение применимо только к исходному коду SQLJ.

Значение по умолчанию true обеспечивает проверку online-семантики.

  •  debugtrue или 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/SQLSQL) — но только после того, как методы будут «опубликованы» при помощи обертки 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. Будьте осторожны!

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

Распространенные заблуждения о...
Распространенные заблуждения о... 1127 просмотров Ирина Светлова Thu, 21 Jun 2018, 18:35:12
Выбор среды для разработки код...
Выбор среды для разработки код... 1231 просмотров Rasen Fasenger Sun, 10 Jun 2018, 14:21:35
Как вызвать код Java из програ...
Как вызвать код Java из програ... 2792 просмотров Максим Николенко Fri, 19 Jan 2018, 06:34:50
Как выполнить / скомпилировать...
Как выполнить / скомпилировать... 2087 просмотров Rasen Fasenger Thu, 21 Jun 2018, 18:32:00

Войдите чтобы комментировать

apv аватар
apv ответил в теме #9655 23 июнь 2020 16:48
Круто! Спасибо за шикарный материал!)