Генерация списка файлов каталога
Пакет UTL_FILE, который мы уже несколько раз использовали по ходу изложения, хорошо справляется с чтением и записью текстовых файлов. Очень часто, однако, необходимо обработать все файлы в указанном каталоге. Этого пакет не позволяет сделать. Для получения списков файлов каталога нет встроенных методов ни в SQL, ни в PL/SQL. На Java его очень легко получить. Вот как это делается:
tkyte@TKYTE816> create global temporary table DIR_LIST 2 (filename varchar2(255)) 3 on commit delete rows 4 / Table created.
В этой реализации я решил использовать для возвращения результатов из хранимой процедуры на Java временную таблицу. Я считаю этот метод наиболее удобным, потому что он позволяет в дальнейшем легко сортировать список и выбирать файлы с нужными именами.
Необходим следующий фрагмент Java-кода:
tkyte@TKYTE816> create or replace 2 and compile java source named "DirList" 3 as 4 import java.io.*; 5 import java.sql.*; 6 7 public class DirList 8 { 9 public static void getList(String directory) 10 throws SQLException 11 { 12 File path = new File(directory); 13 String[] list = path.list(); 14 String element; 15 16 for(int i = 0; i < list.length; i++) 17 { 18 element = list[i]; 19 #sql { INSERT INTO DIR_LIST (FILENAME) 20 VALUES (:element) }; 21 } 22 } 23 24 } 25 /
Java created.
Я решил использовать SQLJ, чтобы сократить программу. Подключение к базе данных уже выполнено, поэтому реализация с помощью интерфейса JDBC потребовала лишь нескольких дополнительных строк кода. Но с помощью препроцессора SQLJ выполнять SQL-операторы в Java так же просто, как и в PL/SQL. Теперь, конечно же, необходимо создать спецификацию вызова:
tkyte@TKYTE816> create or replace 2 procedure get_dir_list(p_directory in varchar2) 3 as language java 4 name 'DirList.getList(java.lang.String)'; 5 /
Procedure created.
Прежде чем запускать эту процедуру, следует учесть еще один нюанс. Необходимо предоставить процедуре право делать то, что она должна — читать список файлов каталога. В данном случае я обладаю правами администратора базы данных, поэтому могу предоставить соответствующие привилегии сам себе, но обычно приходится обращаться с соответствующим запросом к администратору. Если помните, во введении к этой главе я писал:
"... Java-код всегда работает с правами владельца ПО Oracle, поэтому хранимая процедура на Java при предоставлении соответствующих привилегий может переписать файл параметров инициализации сервера, INIT.ORA (или другие, еще более важные файлы, например файлы данных)."
Сервер Oracle защищается от этого следующим образом: для выполнения небезопасных действий необходимо явно получить соответствующую привилегию. Попытавшись использовать эту процедуру до получения необходимых привилегий, мы получим следующее сообщение об ошибке:
tkyte@TKYTE816> exec get_dir_list('c:\temp'); BEGIN get_dir_list('c:\temp'); END;
* ERROR at line 1: ORA-29532: Java call terminated by uncaught Java exception: java.security.AccessControlException: the Permission (java.io.FilePermission c:\temp read) has not been granted by dbms_java.grant_permission to SchemaProtectionDomain(TKYTE|PolicyTableProxy(TKYTE)) ORA-06512: at "TKYTE.GET_DIR_LIST", line 0 ORA-06512: at line 1
Поэтому предоставим себе право получать список файлов в соответствующем каталоге:
tkyte@TKYTE816> begin 2 dbms_java.grant_permission 3 (USER, 4 'java.io.FilePermission', 5 'c:\temp', 6 'read'); 7 end; 8 /
PL/SQL procedure successfully completed.
И можно выполнять процедуру:
tkyte@TKYTE816> exec get_dir_list('c:\temp');
PL/SQL procedure successfully completed.
tkyte@TKYTE816> select * from dir_list where rownum < 5;
FILENAME ---------------------------------- a.sql abc.dat activation activation8i.zip
Соответствующие права доступа определяются спецификацией Java2 Standard Edition (J2SE) и подробно описаны на странице
http://java.sun.com/j2se/1.3/docs/api/java/security/Permission.html. В приложении А мы подробно рассмотрим пакет
DBMS_JAVA и его использование.
Есть еще один нюанс, который необходимо учитывать. Oracle 8.1.6 —
первая версия СУБД Oracle, поддерживающая систему прав доступа, задаваемую спецификацией J2SE. В Oracle 8.1.5 для этого приходилось использовать роли. К сожалению, роль была ровно одна:
JAVASYSPRIV. Ее использование будет подобно предоставлению роли администратора базы данных каждому пользователю только потому, что ему необходимо создать представление, — это слишком мощная роль для выполнения такого простого действия. При наличии роли
JAVASYSPRIV можно делать все, что угодно. Будьте осторожны при использовании этой роли в версии 8.1.5 и постарайтесь перейти на следующие версии, где принята более избирательная модель привилегий.
Хранимые процедуры на языке Java
В сервере Oracle 8.1.5 впервые появилась возможность использовать для реализации хранимых процедур язык Java. Для 99 процентов задач всегда хватало возможностей языка PL/SQL, и его по-прежнему можно использовать. В Oracle 8.0 ранее появилась возможность реализовать процедуры на языке C (см. главу 18). Хранимые процедуры на языке Java (еще один вид внешних процедур) — естественное расширение этой возможности, позволяющее использовать язык Java в тех случаях, когда раньше приходилось программировать на C или C++.
Если необходимо разработать хранимую процедуру, теперь есть как минимум три возможности: использовать язык PL/SQL, Java или C. Я перечислил их в порядке предпочтения. Большую часть обработки в базе данных можно выполнить на PL/SQL. Если что-то нельзя сделать с помощью PL/SQL (в основном это касается интерфейсов с ОС), вступает в игру язык Java. Язык C используется при наличии уже созданного кода на C или в тех случаях, когда нельзя решить задачу средствами Java.
Эта глава не раскрывает основы Java, интерфейса JDBC или программирования с помощью SQLJ. Предполагается, что читатель хоть немного знаком с языком Java и сможет разобраться в небольших фрагментах Java-кода. Предполагается также общее знание интерфейса JDBC и прекомпилятора SQLJ, хотя при наличии минимального опыта использования Java вы легко сможете понять фрагменты кода, связанные с JDBC и SQLJ.
Как работают внешние процедуры на языке Java
Оказывается, внешние процедуры на языке Java (термин "внешняя процедура" в данном случае является синонимом "хранимой процедуры") создавать значительно проще, чем на языке C. Например, в предыдущей главе, посвященной созданию внешних процедур на языке C, пришлось решать следующие проблемы.
Управление состоянием. Внешние процедуры могут потерять информацию о состоянии (текущие значения статических или глобальных переменных). Это связано с используемым механизмом кеширования динамически подключаемых библиотек. Поэтому необходим механизм определения и сохранения состояния в программах на языке C.
Механизмы трассировки. Внешние процедуры выполняются на сервере как отдельный процесс. Хотя на некоторых платформах эти процедуры можно отлаживать с помощью обычного отладчика, это весьма сложно и, если ошибки возникают только при одновременном использовании внешней процедуры большим количеством пользователей, просто невозможно. Необходимо средство генерации трассировочных файлов по требованию, "начиная с этого момента".
Использование параметров. Необходимо средство параметризации внешних процедур, чтобы можно было управлять их работой извне с помощью файла параметров, аналогично тому, как файл init.ora используется для управления сервером.
Общая обработка ошибок. Необходимо простое средство выдачи пользователю вразумительных сообщений об ошибках.
При использовании языка Java оказывается, что управление состоянием, трассировка и общая обработка ошибок уже не является проблемой. Для сохранения информации о состоянии достаточно объявить переменные в создаваемых Java-классах. Для обеспечения простейшей трассировки можно использовать вызовы System.out.println. Общую обработку ошибок можно выполнять с помощью функции RAISE_APPLICATION_ERROR языка PL/SQL. Все это продемонстрировано в следующем коде:
tkyte@TKYTE816> create or replace and compile 2 java source named "demo" 3 as 4 import java.sql.SQLException; 5 6 public class demo extends Object 7 { 8 9 static int counter = 0; 10 11 public static int IncrementCounter() throws SQLException 12 { 13 System.out.println("Входим в функцию IncrementCounter, counter = "+counter); 14 if (++counter >= 3) 15 { 16 System.out.println("Ошибка! counter="+counter); 17 #sql { 18 begin raise_application_error(-20001, 'Слишком много вызовов'); end; 19 }; 20 } 21 System.out.println("Выходим из функции IncrementCounter, counter = "+counter); 22 return counter; 23 } 24 } 25 /
Java created.
Состояние поддерживается с помощью статической переменной
counter. Наша простая демонстрационная программа будет увеличивать счетчик при каждом вызове, а начиная с третьего и при последующих вызовах, будет автоматически выдавать сообщение об ошибке.
Обратите внимание, что для создания небольших фрагментов кода вроде этого можно использовать утилиту SQL*Plus, непосредственно загружающую Java-код в базу данных, автоматически компилируя его в байт-код и запоминая в соответствующих структурах. Ни внешний компилятор, ни средства разработки JDK при этом не нужны — достаточно SQL-оператора
CREATE OR REPLACE. Именно так я и предпочитаю создавать хранимые процедуры на языке Java. Это упрощает их установку на любой платформе. Не нужно запрашивать имя пользователя и пароль, как при использовании команды
LOADJAVA (это утилита командной строки для загрузки исходного кода, классов Java или
jar-файлов в базу данных). Не надо думать о каталогах для поиска классов (
classpath) и других подобных нюансах. В приложении А мы рассмотрим утилиту
LOADJAVA и пакет
DBMS_JAVA, обеспечивающий интерфейс к программе
LOADJAVA.
Этот метод (с использованием оператора
CREATE OR REPLACE) загрузки небольших Java-функций в базу данных особенно хорошо подходит для тех, кто только начинает осваивать технологии Java. Вместо установки JDBC-драйверов, среды разработки JDK, настройки списка каталогов для поиска классов можно просто компилировать код в базе данных, точно так же, как при создании программных единиц PL/SQL. Сообщения об ошибках компиляции выдаются точно так же, как и при использовании языка PL/SQL, например:
tkyte@TKYTE816> create or replace and compile 2 java source named "demo2" 3 as 4 5 public class demo2 extends Object 6 { 7 8 public static int my_routine() 9 { 10 System.out.println("Входим в функцию my_routine"); 11 12 return counter; 13 } 14 } 15 /
Warning: Java created with compilation errors.
tkyte@TKYTE816> show errors java source "demo2" Errors for JAVA SOURCE demo2:
LINE/COL ERROR -------- ---------------------------------------------------- 0/0 demo2:8: Undefined variable: counter 0/0 Info: 1 errors
Это показывает, что функция
my_routine, определенная в строке 8, обращается к необъявленной переменной. Не приходится выискивать ошибку в коде, поскольку получено информативное сообщение о ней. Я не раз убеждался, что многократных ошибок при настройке JDBC/JDK/CLASSPATH можно легко избежать, загрузив за пару секунд код с помощью этого простого подхода.
Вернемся теперь к работающему примеру. Хочу обратить ваше внимание на еще одну важную деталь в созданном выше классе. Метод, вызываемый из языка SQL,
IncrementCounter, объявлен как статический. Он обязательно должен быть статическим. (Хотя не все должно быть статическим: при реализации статического метода можно использовать обычные методы). Для языка SQL необходим хотя бы один метод, который можно вызвать, не передавая неявно данные экземпляра с помощью скрытого параметра, вот почему нужен статический метод.
Теперь, после загрузки небольшого Java-класса, необходимо создать для него спецификацию вызова в языке PL/SQL. Эта процедура очень похожа на ту, что была описана в главе 18 для внешних процедур на языке C, когда мы сопоставляли типы данных C типам данных SQL. Именно это мы и сделаем сейчас; только на этот раз будут сопоставляться типы данных языка Java типам данных SQL:
tkyte@TKYTE816> create or replace 2 function java_counter return number 3 as 4 language java 5 name 'demo.IncrementCounter() return integer'; 6 /
Function created.
Теперь можно вызывать эту функцию:
tkyte@TKYTE816> set serveroutput on
tkyte@TKYTE816> exec dbms_output.put_line(java_counter); 1 PL/SQL procedure successfully completed.
tkyte@TKYTE816> exec dbms_output.put_line(java_counter); 2 PL/SQL procedure successfully completed.
tkyte@TKYTE816> exec dbms_output.put_line(java_counter); BEGIN dbms_output.put_line(java_counter); END;
* ERROR at line 1: ORA-29532: Java call terminated by uncaught Java exception: oracle.jdbc.driver.OracleSQLException:
ORA-20001: Слишком много вызовов
ORA-06512: at line 1 ORA-06512: at "TKYTE.JAVA_COUNTER", line 0 ORA-06512: at line 1
Как видите, информация о состоянии поддерживается автоматически, о чем свидетельствует увеличение счетчика с 1 до 2 и 3. Об ошибках сообщать тоже достаточно легко, но куда попадают результаты обращения к
System.out.println? По умолчанию они попадают в трассировочный файл. При наличии доступа к представлениям
V$PROCESS,
V$SESSION и
V$PARAMETER можно определить имя трассировочного файла в конфигурации выделенного сервера следующим образом (этот пример предназначен для Windows — для ОС UNIX он будет аналогичным, но полученное имя файла будет другим):
tkyte@TKYTE816> select c.value'\ORA'to_char(a.spid,'fm00000')'.trc' 2 from v$process a, v$session b, v$parameter c 3 where a.addr = b.paddr 4 and b.audsid = userenv('sessionid') 5 and c.name = 'user_dump_dest' 6 /
C.VALUE'\ORA'TO_CHAR(A.SPID,'FM00000')'.TRC' ----------------------------------------------------------- C:\oracle\admin\tkyte816\udump\ORA01236.trc
tkyte@TKYTE816> edit C:\oracle\admin\tkyte816\udump\ORA01236.trc
В этом файле можно обнаружить следующее:
Dump file C:\oracle\admin\tkyte816\udump\ORA01236.TRC Tue Mar 27 11:15:48 2001 ORACLE V8.1.6.0.0 - Production vsnsta=0 vsnsql=e vsnxtr=3 Windows 2000 Version 5.0 , CPU type 586 Oracle8i Enterprise Edition Release 8.1.6.0.0 - Production With the Partitioning option JServer Release 8.1.6.0.0 - Production Windows 2000 Version 5.0 , CPU type 586 Instance name: tkyte816 Redo thread mounted by this instance: 1 Oracle process number: 12 Windows thread id: 1236, image: ORACLE.EXE
*** 2001-03-27 11:15:48.820 *** SESSION ID:(8.11) 2001-03-27 11:15:48.810 Входим в функцию IncrementCounter, counter = 0 Выходим из функции IncrementCounter, counter = 1 Входим в функцию IncrementCounter, counter = 1 Выходим из функции IncrementCounter, counter = 2 Входим в функцию IncrementCounter, counter = 2 Ошибка! counter=3 oracle.jdbc.driver.OracleSQLException: ORA-20001: Слишком много вызовов ORA-06512: at line 1 ...
Я также мог бы использовать средства пакета
DBMS_JAVA для перенаправления этих результатов на экран утилиты SQL*Plus, чтобы избежать поиска соответствующего трассировочного файла при отладке функции. В этой главе периодически упоминается пакет
DBMS_JAVA, но полное его описание будет представлено в соответствующем разделе приложения А.
Из этого небольшого примера понятно, что, по сравнению с созданием внешних процедур на языке C, создавать хранимые процедуры на Java — просто. Не нужно специально настраивать сервер — только инсталлировать Java в базу данных. Не нужен внешний компилятор. Многие средства, которые в случае языка C пришлось создавать самим, мы получаем от сервера автоматически. Это на самом деле просто.
Я не описывал пока конфигурирование Java-кода с помощью файла параметров. Причина в том, что Java содержит встроенные средства для этого в виде класса
java.util.Properties. Достаточно использовать метод
load этого класса для загрузки ранее сохраненного набора свойств либо из большого объекта в таблице базы данных, либо из файла ОС, — что больше подходит.
Далее я представлю несколько полезных примеров хранимых процедур на языке Java, в частности, упоминавшихся ранее в разделе "Когда используются хранимые процедуры на языке Java?". Но до этого я хочу переписать представленный в главе 18 пакет
DEMO_PASSING_PKG на языке Java вместо C, чтобы продемонстрировать, как передавать и принимать основные типы данных SQL во внешних процедурах на языке Java.
Когда используются хранимые процедуры на языке Java?
Внешние процедуры на языке Java отличаются от процедур на C тем, что, как и программные единицы PL/SQL, они выполняются встроенной виртуальной Java-машиной (JVM) сервера Oracle, непосредственно в адресном пространстве сервера. Чтобы использовать внешние процедуры на языке C, необходимо сконфигурировать процесс прослушивания, настроить файл TNSNAMES.ORA
и запустить отдельный процесс. При использовании языка Java все это не нужно, поскольку как интерпретируемый язык он считается "безопасным" (как и PL/SQL). Нельзя создать Java-функцию, переписывающую часть области SGA. Это и хорошо, и плохо, как выяснится по ходу обсуждения. Тот факт, что работа происходит в одном адресном пространстве, обеспечивает более быстрое взаимодействие между кодом на Java и сервером, в частности происходит меньше переключений контекста между процессами на уровне ОС. С другой стороны, однако, Java-код всегда работает с правами "владельца ПО Oracle", поэтому хранимая процедура на Java (при наличии соответствующих привилегий) может переписать файл параметров инициализации сервера, INIT.ORA (или другие, еще более важные файлы, например файлы данных).
Лично я постоянно использую небольшие фрагменты Java-кода для реализации того, что невозможно сделать с помощью PL/SQL. Например, в приложении А, посвященном основным стандартным пакетам, я демонстрирую, как я реализовал пакет для работы с сокетами TCP/IP при помощи Java. Я создавал его для версии Oracle 8.1.5, до появления пакета UTL_TCP (который тоже написан на языке Java), и предпочитаю использовать его до сих пор. Я также использую средства языка Java для передачи сообщений электронной почты с сервера. И для этих целей уже существует стандартный пакет, UTL_SMTP (тоже реализованный на языке Java), позволяющий отправлять простые сообщения, но непосредственное использование языка Java открывает множество других возможностей, включая передачу (и получение) сообщений электронной почты с вложениями.
Я интенсивно использую пакет UTL_FILE для чтения и записи файлов в PL/SQL. Одна из возможностей, которых не хватает пакету UTL_FILE, — получение списка файлов в каталоге. С помощью языка PL/SQL его получить нельзя, а на Java — элементарно.
Иногда необходимо выполнить команду ОС или программу из среды сервера. В этом случае язык PL/SQL тоже не поможет, а Java позволит легко решить задачу. Изредка мне необходимо узнать часовой пояс, установленный на сервере. В PL/SQL его получить нельзя, а вот с помощью Java — можно (эту возможность мы рассмотрим в приложении А при изучении стандартного пакета
UTL_TCP). Надо измерять время с точностью до миллисекунд? В Oracle 8i с помощью Java это можно сделать.
Если постоянно необходимо подключаться к СУБД DB2 для выполнения запросов, это можно сделать с помощью шлюза (Transparent Gateway) для СУБД DB2. Это позволит без ограничений выполнять соединения таблиц в разнородных базах данных, распределенные транзакции, прозрачную двухэтапную фиксацию и использовать много других возможностей. Но если необходимо выполнить запрос или изменение в базе данных DB2 и все перечисленные потрясающие возможности не нужны, достаточно загрузить в базу данных драйверы JDBC для DB2, написанные на языке Java, и воспользоваться ими (естественно, это применимо не только для СУБД DB2).
По сути, любой из миллионов имеющихся не интерактивных (не обладающих пользовательским интерфейсом) фрагментов Java-кода можно загрузить в базу данных Oracle и использовать. Вот почему фрагменты Java-кода постоянно встречаются в приложениях.
Я предпочитаю использовать язык Java, только когда это удобно и необходимо. Я по-прежнему считаю PL/SQL подходящим средством для создания подавляющего большинства хранимых процедур. Написав одну-две строки PL/SQL-кода, можно получить тот же результат, что и в случае многострочной программы на Java/JDBC. Препроцессор SQLJ уменьшает объем необходимого кода, но выдаваемый им код по производительности уступает сочетанию языков PL/SQL и SQL. Производительность кода на PL/SQL при взаимодействии с SQL выше, чем для сочетания Java/JDBC, как и можно было предположить. Язык PL/SQL проектировался как расширение SQL, и они очень тесно интегрированы. Большинство типов данных языка PL/SQL — это стандартные типы данных SQL, а все типы данных SQL включены в PL/SQL. Между этими языками нет несоответствия типов. Доступ к SQL из кода на Java выполняется средствами функционального интерфейса, добавленного к языку. Каждый тип данных SQL необходимо преобразовать в некий тип данных Java, и, наоборот, все SQL-операторы выполняются процедурно, т.е. между этими языками нет тесной связи. Итак, если выполняется обработка данных в базе, надо использовать язык PL/SQL. Если надо на время выйти за пределы базы данных (например, чтобы отправить сообщение по электронной почте), лучшим средством для этого является язык Java. Если необходимо выполнить поиск в сообщениях электронной почты, хранящихся в базе данных, используйте язык PL/SQL. Если же необходимо загрузить сообщения электронной почты в базу данных, используйте Java.
ORA-29531 no method X in class Y
Если в рассмотренном ранее примере RunThis изменить спецификацию вызова следующим образом:
tkyte@TKYTE816> create or replace 2 function RUN_CMD(p_cmd in varchar2) return number 3 as 4 language java 5 name 'Util.RunThis(String[]) return integer'; 7 /
Function created.
будет выдано сообщение об ошибке ORA-29531. Обратите внимание, что в списке параметров функции Util.RunThis, я указал тип данных String, а не java.lang.String.
tkyte@TKYTE816> exec rc('c:\winnt\system32\cmd.exe /c dir') java.lang.NullPointerException at oracle.aurora.util.JRIExtensions.getMaximallySpecificMethod(JRIExtensions.java) at oracle.aurora.util.JRIExtensions.getMaximallySpecificMethod(JRIExtensions.java) BEGIN RC('c:\winnt\system32\cmd.exe /c dir'); END;
* ERROR at line 1: ORA-29531: no method RunThis in class Util ORA-06512: at "TKYTE.RUN_CMD", line 0 ORA-06512: at "TKYTE.RC", line 5 ORA-06512: at line 1
Дело в том, что для успешного сопоставления типов данных должны указываться полные (fully qualified) имена типов. Хотя класс java.lang неявно импортируется в Java-программах, он не импортируется на уровне языка SQL. Получив это сообщение об ошибке, необходимо проверить сопоставление типов данных и убедиться, что используются полные имена типов данных Java и что они в точности совпадают с именами имеющихся типов данных. Соответствующий Java-метод определяется по сигнатуре, а сигнатура создается на основе используемых типов данных. Минимальное различие в типах входных данных, результатов или регистре символов в имени приведет к несовпадению сигнатур, и сервер Oracle не найдет соответствующий код.
ORA-29549 Java Session State Cleared
По ходу разработки можно столкнуться с сообщениями следующего вида:
select my_timestamp, to_char(sysdate,'yyyy-mm-dd hh24:mi:ss') from dual * ERROR at line 1: ORA-29549: class TKYTE.MyTimestamp has changed, Java session state cleared
Это означает, что использованный в сеансе класс был перекомпилирован (скорее всего — вами же). Вся связанная с этим классом информация о состоянии потеряна. Достаточно повторно выполнить оператор, при выполнении которого было выдано это сообщение, и информация о состоянии обновится.
По этой причине следует избегать повторной загрузки Java-классов в действующей производственной системе. После этого использующий Java-класс сеанс при обращении к нему получит такое сообщение об ошибке.
Ошибки прав доступа
Мы уже знакомы с таким сообщением:
ERROR at line 1: ORA-29532: Java call terminated by uncaught Java exception: java.security.AccessControlException: the Permission (java.io.FilePermission c:\temp read) has not been granted by dbms_java.grant_permission to SchemaProtectionDomain(TKYTE|PolicyTableProxy(TKYTE)) ORA-06512: at "TKYTE.GET_DIR_LIST", line 0 ORA-06512: at line 1
К счастью, в тексте сообщения об ошибке явно указано, какие привилегии необходимо получить, чтобы вызов был успешным. Обладающий соответствующими привилегиями пользователь должен предоставить вам эти привилегии с помощью процедуры GRANT_PERMISSION пакета DBMS_JAVA.
Передача данных
В этом примере я собираюсь создать ряд процедур с параметром, передаваемым в режиме IN, и параметром, передаваемым в режиме OUT (или IN OUT). Мы напишем по процедуре для каждого из интересующих нас типов данных (наиболее часто используемых). При этом будет продемонстрирован правильный способ передачи входных данных и получения результатов каждого типа. Кроме того, я создам несколько функций и покажу, как возвращать данные некоторых из этих типов. Меня при работе с Java интересуют следующие типы:
строки (размером до 32 Кбайт);
числа (произвольного масштаба и точности);
даты;
целые числа (включая данные типа binary_integer);
данные типа RAW (размером до 32 Кбайт);
большие объекты (для любых данных размером более 32 Кбайт);
массивы строк;
массивы чисел;
массивы дат.
Этот список несколько отличается от аналогичного списка для внешних процедур на языке C. В частности, в нем не указан тип данных BOOLEAN. Дело в том, что пока нет соответствия между типом данных PL/SQL BOOLEAN и типами данных языка Java. Нельзя передавать данные типа BOOLEAN как параметры внешним процедурам, написанным на языке Java.
С помощью объектно-реляционных расширений можно создавать типы данных любой сложности. Для создания таких типов данных я рекомендую использовать поставляемое корпорацией Oracle Java-средство JPublisher. Оно автоматически создает Java-классы, соответствующие объектным типам. Подробнее о JPublisher можно почитать в руководстве Oracle8i JPublisher User's Guide, которое входит в набор документации, предлагаемой корпорацией Oracle. Как и в случае внешних процедур на языке C, мы не будем углубляться в особенности использования объектных типов в хранимых процедурах на Java, ограничившись только простыми наборами данных скалярных типов.
Java-класс будет создан для тех же целей, что и представленная в предыдущей главе динамически подключаемая библиотека на языке C. Начнем с SQL-операторов для создания трех типов наборов — они совпадают с использовавшимися в примере для языка С в предыдущей главе:
tkyte@TKYTE816> create or replace type numArray as table of number; Type created.
tkyte@TKYTE816> create or replace type dateArray as table of date; Type created.
tkyte@TKYTE816> create or replace type strArray as table of varchar2(255); Type created.
Теперь рассмотрим спецификацию PL/SQL-пакета для этого примера. Она будет состоять из набора перегруженных процедур и функций для проверки приема и передачи параметров в хранимых процедурах на языке Java. Каждая подпрограмма имеет параметр, предаваемый в режиме
IN, и параметр, передаваемый в режиме
OUT, что позволяет продемонстрировать передачу данных в Java-код и возвращение результатов.
Первая процедура передает числовые данные. Данные Oracle типа
NUMBER
будут передаваться как Java-тип
BigDecimal. Их можно принимать и как данные типа
int, и как строки и как другие типы, но при этом возможна потеря точности. Данные типа
BigDecimal могут без проблем принять любое значение типа
NUMBER от сервера Oracle.
Обратите внимание, что параметр, передаваемый в режиме
OUT, на уровне Java принимается как массив данных типа
BigDecimal. Так будет для всех параметров, передаваемых Java в режиме
OUT. Для изменения параметра, переданного Java, нужно передавать "массив" параметров (в этом массиве будет только один элемент) и изменять соответствующий элемент массива. Далее, при описании кода на языке Java, вы увидите, как это сказывается на исходном коде.
tkyte@TKYTE816> create or replace package demo_passing_pkg 2 as 3 procedure pass(p_in in number, p_out out number) 4 as 5 language java 6 name 'demo_passing_pkg.pass(java.math.BigDecimal, 7 java.math.BigDecimal[])'
Даты Oracle сопоставляются типу данных
Timestamp. И в этом случае можно было бы сопоставить датам множество других типов, например
String, но во избежание потери информации при неявных преобразованиях я выбрал тип
Timestamp, который позволяет сохранить все данные, содержащиеся в объектах Oracle типа
DATE.
8 9 procedure pass(p_in in date, p_out out date) 10 as 11 language java 12 name 'demo_passing_pkg.pass(java.sql.Timestamp, 13 java.sql.Timestamp[])';
Строки типа
VARCHAR2 передаются очень просто — как данные типа
java.lang.String.
14 15 procedure pass( p_in in varchar2, p_out out varchar2) 16 as 17 language java 18 name 'demo_passing_pkg.pass(java.lang.String, 19 java.lang.String[])';
Для данных типа
CLOB мы используем предоставляемый Oracle Java-тип
oracle.sql.CLOB. С помощью этого типа мы сможем получить входной и выходной потоки данных, используемые для чтения и записи данных типа
CLOB.
20 21 procedure pass(p_in in CLOB, p_out in out CLOB) 22 as 23 language java 24 name 'demo_passing_pkg.pass(oracle.sql.CLOB, 25 oracle.sql.CLOB[])';
Теперь перейдем к наборам: вы видели, что, независимо от типа фактически передаваемого набора, используется один и тот же предоставляемый Oracle тип. Вот почему в данном случае Java-функции не являются перегруженными, как все предыдущие (пока что все вызываемые Java-функции назывались
demo_passing_pkg.pass). Поскольку все типы наборов передаются как один и тот же тип Java, перегрузку имен использовать нельзя — необходимо называть функцию в соответствии с реально передаваемым типом данных:
26 27 procedure pass(p_in in numArray, p_out out numArray) 28 as 29 language java 30 name 'demo_passing_pkg.pass_num_array(oracle.sql.ARRAY, 31 oracle.sql.ARRAY[])'; 32 33 procedure pass(p_in in dateArray, p_out out dateArray) 34 as 35 language java 36 name 'demo_passing_pkg.pass_date_array(oracle.sql.ARRAY, 37 oracle.sql.ARRAY[])'; 38 39 procedure pass(p_in in strArray, p_out out strArray) 40 as 41 language java 42 name 'demo_passing_pkg.pass_str_array(oracle.sql.ARRAY, 43 oracle.sql.ARRAY[])';
Следующие две процедуры демонстрируют сопоставление для типов
RAW
и
INT. SQL-тип
RAW будет сопоставляться встроенному типу
byte
языка Java. Для целых чисел будет использоваться встроенный тип данных
int языка Java:
44 45 procedure pass_raw(p_in in RAW, p_out out RAW) 46 as 47 language java 48 name 'demo_passing_pkg.pass(byte[], byte[][])'; 49 50 procedure pass_int(p_in in number, 51 p_out out number) 52 as 53 language java 54 name 'demo_passing_pkg.pass_int(int, int[])';
Наконец, для полноты изложения я продемонстрирую использование функций для возвращения данных простых скалярных типов:
55 56 function return_number return number 57 as 58 language java 59 name 'demo_passing_pkg.return_num() return java.math.BigDecimal'; 60 61 function return_date return date 62 as 63 language java 64 name 'demo_passing_pkg.return_date() return java.sql.Timestamp'; 65 66 function return_string return varchar2 67 as 68 language java 69 name 'demo_passing_pkg.return_string() return java.lang.String'; 70 71 end demo_passing_pkg; 72 /
Package created.
Эта спецификация пакета практически совпадает (за исключением процедур для данных типа
BOOLEAN) с той, что использовалась для внешних процедур на языке C. В этом примере я поместил уровень связывания непосредственно в спецификацию, чтобы не пришлось писать избыточное тело пакета (все функции написаны на языке Java).
Рассмотрим Java-код, реализующий использованные выше функции. Начнем с определения Java-класса
demo_passing_pkg:
tkyte@TKYTE816> set define off
tkyte@TKYTE816> create or replace and compile 2 java source named "demo_passing_pkg" 3 as 4 import java.io.*; 5 import java.sql.*; 6 import java.math.*; 7 import oracle.sql.*; 8 import oracle.jdbc.driver.*; 9 10 public class demo_passing_pkg extends Object 11 {
В первом из представленных далее методов демонстрируется единственно возможный способ передачи параметров в режиме
OUT функции на Java; фактически мы передаем массив из одного элемента. При изменении значения в массиве изменяется параметр, переданный в режиме
OUT. Вот почему все эти методы в качестве второго параметра принимают массив. Значение
p_out[0] можно изменять, и оно будет передано методом в вызывающую среду. Изменения значения
p_in в вызывающую среду не передаются.
Интересная особенность данного метода — отсутствие индикаторной переменной. Язык Java поддерживает понятие неопределенного объекта (
null) в объектных типах, как и языки SQL и PL/SQL. Он, однако, не поддерживает трехзначную логику, как SQL; операции
X IS NOT NULL нет — можно только непосредственно сравнивать объект с
null. Не перепутайте и не пытайтесь писать условия вида
p_in <> NULL в PL/SQL-коде, поскольку они не будут работать корректно.
12 public static void pass(java.math.BigDecimal p_in, 13 java.math.BigDecimal[] p_out) 14 { 15 if (p_in != null) 16 { 17 System.out.println 18 ("Первый параметр " + p_in.toString()); 19 20 p_out[0] = p_in.negate(); 21 22 System.out.println 23 ("Устанавливаем параметр out равным " + p_out[0].toString()); 24 } 25 }
Следующий метод работает с типом данных Oracle
DATE. Он совпадает с представленным выше, за исключением того, что используются методы класса
Timestamp для обработки даты. Наша задача — добавить к переданной дате один месяц:
26 27 public static void pass(java.sql.Timestamp p_in, 28 java.sql.Timestamp[] p_out) 29 { 30 if (p_in != null) 31 { 32 System.out.println 33 ("Первый параметр " + p_in.toString()); 34 35 p_out[0] = p_in; 36 37 if (p_out[0].getMonth() < 11) 38 p_out[0].setMonth(p_out[0].getMonth()+1); 39 else 40 { 41 p_out[0].setMonth(0); 42 p_out[0].setYear(p_out[0].getYear()+1); 43 } 44 System.out.println 45 ("Устанавливаем параметр out равным " + p_out[0].toString()); 46 } 47 }
Теперь переходим к самому простому из типов данных —
String, который соответствует строковым типам SQL. Если вспомнить версию на языке C, с шестью формальными параметрами, индикаторными переменными, атрибутами
strlen, функциями
strcpy и т.п., то по сравнению с ней эта реализация тривиальна:
48 49 public static void pass(java.lang.String p_in, 50 java.lang.String[] p_out) 51 { 52 if (p_in != null) 53 { 54 System.out.println 55 ("Первый параметр " + p_in.toString()); 56 57 p_out[0] = p_in.toUpperCase(); 58 59 System.out.println 60 ("Устанавливаем параметр out равным " + p_out[0].toString()); 61 } 62 }
В методе для данных типа
CLOB придется выполнить ряд дополнительных действий. Для того чтобы показать, как принимать и возвращать большие объекты, здесь выполняется копирование. Вы видите, что для изменения/чтения содержимого большого объекта используются стандартные потоки чтения/записи языка Java. В этом примере
is — входной поток, а
os — выходной. Метод копирует данные фрагментами по 8 Кбайт. Выполняется цикл чтения и записи, пока не закончатся считываемые данные:
63 64 public static void pass(oracle.sql.CLOB p_in, 65 oracle.sql.CLOB[] p_out) 66 throws SQLException, IOException 67 { 68 if (p_in != null && p_out[0] != null) 69 { 70 System.out.println 71 ("Первый параметр " + p_in.length()); 72 System.out.println 73 ("Первый параметр '" + 74 p_in.getSubString(1,80) + "'"); 75 76 Reader is = p_in.getCharacterStream(); 77 Writer os = p_out[0].getCharacterOutputStream(); 78 79 char buffer[] = new char[8192]; 80 int length; 81 82 while((length=is.read(buffer,0,8192)) != -1) 83 os.write(buffer,0,length); 84 85 is.close(); 86 os.close(); 87 88 System.out.println 89 ("Устанавливаем параметр out равным " + 90 p_out[0].getSubString(1,80)); 91 } 92 }
Следующий метод — приватный (внутренний). Он выдает метаданные о переданном ему объекте типа
oracle.sql.ARRAY. Для каждого из передаваемых Java трех типов массивов будет вызываться этот метод, информирующий о том, какого размера и типа массив передан:
93 94 private static void show_array_info(oracle.sql.ARRAY p_in) 95 throws SQLException 96 { 97 System.out.println("Тип массива " + 98 p_in.getSQLTypeName()); 99 System.out.println("Код типа массива " + 100 p_in.getBaseType()); 101 System.out.println("Длина массива " + 102 p_in.length()); 103 }
Теперь рассмотрим методы для обработки этих массивов. Использовать массивы несложно, если разобраться, как получать из них данные и изменять их. Получить данные очень просто; метод
getArray() возвращает базовый массив данных. Приведя возвращаемое методом
getArray() значение к нужному типу, мы получим Java-массив этого типа. Поместить данные в такой массив немного сложнее. Необходимо сначала получить дескриптор (метаданные) массива, а затем создать новый объект-массив с этим дескриптором и соответствующими значениями. Следующий набор методов продемонстрирует это для каждого из использованных типов массивов. Обратите внимание, что тексты методов практически совпадают, за исключением фактических обращений к массивам данных Java. Эти методы выдают метаданные для типа
oracle.sql.ARRAY, выдают содержимое массива и копируют входной массив в выходной:
104 105 public static void pass_num_array(oracle.sql.ARRAY p_in, 106 oracle.sql.ARRAY[] p_out) 107 throws SQLException 108 { 109 show_array_info(p_in); 110 java.math.BigDecimal[] values = (BigDecimal[])p_in.getArray(); 111 112 for(int i = 0; i < p_in.length(); i++) 113 System.out.println("p_in["+i+"] = " + values[i].toString()); 114 115 Connection conn = new OracleDriver().defaultConnection(); 116 ArrayDescriptor descriptor = 117 ArrayDescriptor.createDescriptor(p_in.getSQLTypeName(), conn); 118 119 p_out[0] = new ARRAY(descriptor, conn, values); 120 121 } 122 123 public static void 124 pass_date_array(oracle.sql.ARRAY p_in, oracle.sql.ARRAY[] p_out) 125 throws SQLException 126 { 127 show_array_info(p_in); 128 java.sql.Timestamp[] values = (Timestamp[])p_in.getArray(); 129 130 for(int i = 0; i < p_in.length(); i++) 131 System.out.println("p_in["+i+"] = " + values[i].toString()); 132 133 Connection conn = new OracleDriver().defaultConnection(); 134 ArrayDescriptor descriptor = 135 ArrayDescriptor.createDescriptor(p_in.getSQLTypeName(), conn); 136 137 p_out[0] = new ARRAY(descriptor, conn, values); 138 139 } 140 141 public static void 142 pass_str_array(oracle.sql.ARRAY p_in, oracle.sql.ARRAY[] p_out) 143 throws java.sql.SQLException,IOException 144 { 145 show_array_info(p_in); 146 String[] values = (String[])p_in.getArray(); 147 148 for(int i = 0; i < p_in.length(); i++) 149 System.out.println("p_in["+i+"] = " + values[i]); 150 151 Connection conn = new OracleDriver().defaultConnection(); 152 ArrayDescriptor descriptor = 153 ArrayDescriptor.createDescriptor(p_in.getSQLTypeName(), conn); 154 155 p_out[0] = new ARRAY(descriptor, conn, values); 156 157 }
Передача данных типа
RAW ничем не отличается от передачи строк. С этим типом данных работать очень легко:
158 159 public static void pass(byte[] p_in, byte[][] p_out) 160 { 161 if (p_in != null) 162 p_out[0] = p_in; 163 }
Передача целых чисел —
проблематична, я вообще не рекомендую их передавать. Нет способа передать значение
NULL — соответствующий тип данных
int относится к базовым типам данных языка Java. Эти данные не являются объектами и поэтому не могут быть неопределенными. Поскольку индикаторные переменные не поддерживаются, то при необходимости обработать неопределенные значения придется передавать отдельный параметр, а в PL/SQL-коде — проверять соответствующий флаг, чтобы определить, не возвращено ли неопределенное значение. Соответствующий метод представлен здесь для полноты, но лучше вообще не использовать данные целого типа, особенно как параметры, передаваемые в режиме
IN, - Java- метод не сможет определить, что значение не нужно читать, поскольку неопределенные значения не поддерживаются.
164 165 public static void pass_int(int p_in, int[] p_out) 166 { 167 System.out.println 168 ("Входной параметр " + p_in); 169 170 p_out[0] = p_in; 171 172 System.out.println 173 ("Выходной параметр " + p_out[0]); 174 }
Наконец, перейдем к функциям. Если помните, на языке C написать их было непросто. Необходимо было выделять память, обрабатывать неопределенные значения, явно преобразовывать типы данных C в типы данных Oracle и т.д. Каждая C-функция при этом состояла как минимум из десятка строк кода. В случае же Java достаточно добавить оператор
return:
175 176 public static String return_string() 177 { 178 return "Hello World"; 179 } 180 181 public static java.sql.Timestamp return_date() 182 { 183 return new java.sql.Timestamp(0); 184 } 185 186 public static java.math.BigDecimal return_num() 187 { 188 return new java.math.BigDecimal("44.3543"); 189 } 190 191 } 192 /
Java created
tkyte@TKYTE816> set define on
Запрограммировать функцию на Java гораздо проще, чем на языке C, благодаря тому, что Java выполняет много действий автоматически, "за кадром". Для обеспечения аналогичной функциональности на языке C потребовалось около 1000 строк кода. Выделение памяти, которое требует столько внимания при программировании на C, в случае Java — не проблема. В случае ошибки возбуждается исключительная ситуация. Индикаторные переменные, с которыми надо было возиться в языке C, вообще не нужны в Java. Проблема возникает при передаче типов данных, соответствующих не объектным типам Java, но, как я уже говорил, не следует их использовать, если может потребоваться передать неопределенные значения.
Поскольку все компоненты созданы, можно вызывать подпрограммы. Например:
tkyte@TKYTE816> set serveroutput on size 1000000 tkyte@TKYTE816> exec dbms_java.set_output(1000000)
tkyte@TKYTE816> declare 2 l_in strArray := strArray(); 3 l_out strArray := strArray(); 4 begin 5 for i in 1 .. 5 loop 6 l_in.extend; 7 l_in(i) := 'Элемент ' i; 8 end loop; 9 10 demo_passing_pkg.pass(l_in, l_out); 11 for i in 1 .. l_out.count loop 12 dbms_output.put_line('l_out(' i ') = ' l_out(i)); 13 end loop; 14 end; 15 / Тип массива SECOND.STRARRAY Код типа массива 12 Длина массива 5 p_in[0] = Элемент 1 p_in[1] = Элемент 2 p_in[2] = Элемент 3 p_in[3] = Элемент 4 p_in[4] = Элемент 5 l_out(1) = Элемент 1 l_out(2) = Элемент 2 l_out(3) = Элемент 3 l_out(4) = Элемент 4 l_out(5) = Элемент 5
PL/SQL procedure successfully completed.
Первые восемь строк результата были сгенерированы Java-методом, а последние пять — PL/SQL-кодом. Значит, мы передали массив из PL/SQL в Java и получили его обратно. С помощью Java-метода мы скопировали входной массив в выходной после распечатки метаданных и значений элементов массива.
Полезные примеры
Я свято верю, что, если задачу можно решить с помощью одного SQL-оператора, это надо делать. Никогда не используйте, например, цикл FOR по курсору, если достаточно выполнить оператор UPDATE. Если задачу нельзя решить в SQL, попытайтесь решить ее в PL/SQL. Никогда не пишите внешнюю процедуру на языке Java или C, разве что задачу нельзя решить в PL/SQL или реализация на языке C существенно повышает производительность. Если по техническим причинам задачу нельзя решить с помощью PL/SQL, попробуйте решить ее на языке Java. Однако использование Java требует дополнительных ресурсов — памяти, процессорного времени и времени на первоначальный запуск виртуальной машины JVM. Использование PL/SQL также требует дополнительных ресурсов, но они уже выделены, ничего дополнительно запускать не надо.
Тем не менее ряд задач нельзя решить с помощью языка PL/SQL, а при использовании Java они решаются элементарно. Ниже представлены полезные фрагменты Java-кода, используемые мной в повседневной практике. Это, конечно, не исчерпывающий список, а лишь вершина айсберга. В приложении А примеры использования языка Java в Oracle рассмотрены более широко.
Получение времени с точностью до миллисекунд
Примеры становятся все меньше, короче и выполняются быстрее. Это я и хочу подчеркнуть. С помощью небольших фрагментов Java-кода, примененных в соответствующих местах, можно существенно расширить функциональные возможности.
В Oracle 9i эта функция станет избыточной, поскольку эта версия поддерживает временные отметки с точностью менее секунды. Но при необходимости такая точность измерения времени достижима и в предыдущих версиях:
tkyte@TKYTE816> create or replace java source 2 named "MyTimestamp" 3 as 4 import java.lang.String; 5 import java.sql.Timestamp; 6 7 public class MyTimestamp 8 { 9 public static String getTimestamp() 10 { 11 return (new 12 Timestamp(System.currentTimeMillis())).toString(); 13 } 14 }; 15 /
Java created.
tkyte@TKYTE816> create or replace function my_timestamp return varchar2 2 as language java 3 name 'MyTimestamp.getTimestamp() return java.lang.String'; 4 /
Function created.
tkyte@TKYTE816> select my_timestamp, 2 to_char(sysdate,'yyyy-mm-dd hh24:mi:ss') from dual 3 /
MY_TIMESTAMP TO_CHAR(SYSDATE,'YY ------------------------- ------------------- 2001-03-27 19:15:59.688 2001-03-27 19:15:59
В этой главе вы узнали,
В этой главе вы узнали, как реализовать хранимые процедуры на языке Java. Это не означает, что весь существующий код на языке PL/SQL необходимо переписать в виде хранимых процедур на Java. Но если, программируя на PL/SQL, вы столкнетесь с неразрешимой проблемой, что обычно происходит при необходимости выйти за пределы базы данных и обеспечить взаимодействие с операционной системой, попробуйте решить задачу с помощью языка Java.
Благодаря полученным в этой главе сведениям вы сможете передать основные типы данных SQL, в том числе массивы, с уровня PL/SQL на уровень Java-методов и получить результаты. Я представил несколько полезных фрагментов Java-кода, которые можно использовать непосредственно; обратившись к документации по языку Java, вы обнаружите десятки других фрагментов, незаменимых при разработке приложений.
При осторожном использовании, программирование на Java может существенно расширить возможности разработки приложений.
Возможные ошибки
Большинство сообщений об ошибках, которые вы получите при использовании хранимых процедур на Java, связаны с компиляцией кода и несоответствием типов параметров. Некоторые из наиболее типичных сообщений об ошибках рассмотрены ниже.
Выполнение команды ОС
Если бы я получал десятицентовую монету всякий раз, отвечая на вопрос о том, как выполнить команду ОС! До появления поддержки языка Java в СУБД, это действительно было сложно. Теперь же это почти тривиально. Есть, вероятно, сотни способов сделать это, но следующий фрагмент кода работает вполне удовлетворительно:
tkyte@TKYTE816> create or replace and compile 2 java source named "Util" 3 as 4 import java.io.*; 5 import java.lang.*; 6 7 public class Util extends Object 8 { 9 10 public static int RunThis(String[] args) 11 { 12 Runtime rt = Runtime.getRuntime(); 13 int rc = -1; 14 15 try 16 { 17 Process p = rt.exec(args[0]); 18 19 int bufSize = 4096; 20 BufferedInputStream bis = 21 new BufferedInputStream(p.getInputStream(), bufSize); 22 int len; 23 byte buffer[] = new byte[bufSize]; 24 25 // Выдаем то, что получено программой 26 while ((len = bis.read(buffer, 0, bufSize)) != -1) 27 System.out.write(buffer, 0, len); 28 29 rc = p.waitFor(); 30 } 31 catch (Exception e) 32 { 33 e.printStackTrace(); 34 rc = -1; 35 } 36 finally 37 { 38 return rc; 39 } 40 } 41 } 42 /
Java created.
Он позволяет выполнить любую программу и получить ее результаты либо в трассировочном файле на сервере, либо, при использовании средств пакета DBMS_JAVA, в буфере пакета DBMS_OUTPUT. Это, однако, весьма мощное средство — при наличии соответствующих привилегий с его помощью можно выполнять любую команду от имени пользователя-владельца ПО Oracle. В данном случае я хочу иметь возможность получить список процессов с помощью утилиты /usr/bin/ps в ОС UNIX и tlist.exe в Windows. Для этого мне необходимы две привилегии:
tkyte@TKYTE816> BEGIN 2 dbms_java.grant_permission 3 (USER, 4 'java.io.FilePermission', 5 -- '/usr/bin/ps', -- для UNIX 6 c:\bin\tlist.exe', -- для WINDOWS 7 'execute'); 8 9 dbms_java.grant_permission 10 (USER, 11 'java.lang.RuntimePermission', 12 '*', 13 'writeFileDescriptor'); 14 end; 15 /
PL/SQL procedure successfully completed.
В вашей системе может отсутствовать утилита tlist.exe. Она входит в состав набора инструментальных средств Windows Resource Toolkit и доступна не во всех Windows-системах. Этот пример просто показывает, что можно сделать, — отсутствие доступа к tlist.exe не помешает демонстрации. Этот метод можно использовать для выполнения любой программы. Учтите, однако, что нужно быть внимательным, предоставляя права на выполнение программ с помощью пакета DBMS_JAVA. Например, предоставление права на выполнение программы c:\winnt\system32\cmd.exe фактически означает разрешение выполнять ВСЕ программы, что очень опасно.
Первый вызов dbms_java.grant_permission позволяет запускать одну конкретную программу. При желании можно рискнуть и указать вместо имени программы символ *. Это позволит выполнять любые программы. Я не думаю, однако, что это разумно; явно перечисляйте полные имена программ, в надежности которых вы уверены. Вторая привилегия позволяет генерировать результаты во время выполнения. Здесь придется использовать метасимвол *, поскольку я не знаю точно, куда именно будут выдаваться результаты (в стандартный выходной поток, stdout, например, или куда-нибудь еще).
Теперь необходимо создать уровень связывания:
tkyte@TKYTE816> create or replace 2 function RUN_CMD(p_cmd in varchar2) return number 3 as 4 language java 5 name 'Util.RunThis(java.lang.String[]) return integer'; 6 /
Function created.
tkyte@TKYTE816> create or replace procedure rc( 2 as 3 x number; 4 begin 5 x := run_cmd(p_cmd); 6 if (x <> 0) 7 then 8 raise program_error; 9 end if; 10 end; 11 /
Procedure created.
Здесь я создал еще один уровень абстракции выше функции связывания, чтобы можно было выполнять программу как процедуру. Давайте посмотрим, как это работает:
tkyte@TKYTE816> set serveroutput on size 1000000 tkyte@TKYTE816> exec dbms_java.set_output(1000000)
PL/SQL procedure successfully completed.
tkyte@TKYTE816> exec rc('C:\WINNT\system32\cmd.exe /c dir') Volume in drive C has no label. Volume Serial Number is F455-B3C3 Directory of C:\oracle\DATABASE 05/07/2001 10:13a <DIR> . 05/07/2001 10:13a <DIR> .. 11/04/2000 06:28p <DIR> ARCHIVE 11/04/2000 06:37p 47 inittkyte816.ora 11/04/2000 06:28p 31,744 ORADBA.EXE 05/07/2001 09:07p 1,581 oradim.log 05/10/2001 07:47p 2,560 pwdtkyte816.ora 05/06/2001 08:43p 3,584 pwdtkyte816.ora.hold 01/26/2001 11:31a 3,584 pwdtkyte816.xxx 04/19/2001 09:34a 21,309 sqlnet.log 05/07/2001 10:13a 2,424 test.sql 01/30/2001 02:10p 348,444 xml.tar 9 File(s) 415,277 bytes 3 Dir(s) 13,600,501,760 bytes free
PL/SQL procedure successfully completed.
Мы получили список файлов каталога ОС.