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