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

       

Как работают внешние процедуры на языке 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.


    Содержание раздела