Вы находитесь на страницах старой версии сайта.
Переходите на новую версию Interface.Ru

Oracle для профессионалов. Глава 19

© Том Кайт
Статья была опубликована на сайте www.citforum.ru

Хранимые процедуры на языке 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 тем, что, как и программные единицы 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.

Как работают внешние процедуры на языке Java

Оказывается, внешние процедуры на языке Java (термин "внешняя процедура" в данном случае является синонимом "хранимой процедуры") создавать значительно проще, чем на языке C. Например, в предыдущей главе, посвященной созданию внешних процедур на языке C, пришлось решать следующие проблемы.

При использовании языка 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.

Передача данных

В этом примере я собираюсь создать ряд процедур с параметром, передаваемым в режиме IN, и параметром, передаваемым в режиме OUT (или IN OUT). Мы напишем по процедуре для каждого из интересующих нас типов данных (наиболее часто используемых). При этом будет продемонстрирован правильный способ передачи входных данных и получения результатов каждого типа. Кроме того, я создам несколько функций и покажу, как возвращать данные некоторых из этих типов. Меня при работе с Java интересуют следующие типы:

Этот список несколько отличается от аналогичного списка для внешних процедур на языке 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 рассмотрены более широко.

Генерация списка файлов каталога

Пакет 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 в СУБД, это действительно было сложно. Теперь же это почти тривиально. Есть, вероятно, сотни способов сделать это, но следующий фрагмент кода работает вполне удовлетворительно:

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.

Мы получили список файлов каталога ОС.

Получение времени с точностью до миллисекунд

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

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.

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 не найдет соответствующий код.

Резюме

В этой главе вы узнали, как реализовать хранимые процедуры на языке Java. Это не означает, что весь существующий код на языке PL/SQL необходимо переписать в виде хранимых процедур на Java. Но если, программируя на PL/SQL, вы столкнетесь с неразрешимой проблемой, что обычно происходит при необходимости выйти за пределы базы данных и обеспечить взаимодействие с операционной системой, попробуйте решить задачу с помощью языка Java.

Благодаря полученным в этой главе сведениям вы сможете передать основные типы данных SQL, в том числе массивы, с уровня PL/SQL на уровень Java-методов и получить результаты. Я представил несколько полезных фрагментов Java-кода, которые можно использовать непосредственно; обратившись к документации по языку Java, вы обнаружите десятки других фрагментов, незаменимых при разработке приложений.

При осторожном использовании, программирование на Java может существенно расширить возможности разработки приложений.

Дополнительная информация

За дополнительной информацией обращайтесь в компанию Interface Ltd.

Обсудить на форуме Oracle

Рекомендовать страницу

INTERFACE Ltd.
Телефон/Факс: +7 (495) 925-0049
Отправить E-Mail
http://www.interface.ru
Rambler's Top100
Ваши замечания и предложения отправляйте редактору
По техническим вопросам обращайтесь к вебмастеру
Дата публикации: 03.02.06