Использование Java классов и объектов в Oracle8i
Использование Java классов и объектов в Oracle8i
1 июля 2000 г.
(Oracle Magazine, no.3, 2000)
Стивен Ферстайн
Эта статья является второй из серии, рассматривающей методику, связанную с использованием Java из окружения, написанного на PL/SQL. В предыдущей статье я описывал, как конструировать простые Java-классы для доступа к основным функциональным возможностям Java, загружать Java-классы в программы базы данных Oracle, управлять этими новыми объектами базы данных, и публиковать их для использования в PL/SQL. В этой статье я продолжу этот вводный курс, подробно исследуя “поддерживаемые черты” - пространство Java-имен в Oracle8i, проверка и экспорт элементов кода Java, хранимых в базе данных, возможность вывода на экран из Java-методов, и обработка исключений для Java-методов из PL/SQL.
Что в имени?
Oracle8i с виртуальной машиной Java (VM) хранит каждый Java-класс как объект схемы. Имя объекта выводится из полного квалифицированного имени класса (но не идентично ему), и включает имена всех содержащих пакетов. Например, полным квалифицированным именем класса OracleSimpleChecker является Oracle.sqlj.checker.OracleSimpleChecker
Однако, в базе данных Oracle полным квалифицированным именем Java-объекта схемы будет следующее: oracle/sqlj/checker/OracleSimpleChecker
Другими словами, при сохранении Java-класса в базе данных Oracle, точки заменяются слешами.
Имя объекта в базе данных Oracle, вне зависимости от того, является ли этот объект таблицей или классом, не может быть длиннее 30 символов. Java не имеет таких ограничений. Вы можете загрузить Java-класс с именем более 4000 символов в базу данных Oracle. Если имя элемента Java содержит более 30 символов, то программное обеспечение Oracle автоматически генерирует корректный (менее 31 символа) псевдоним, или “короткое имя” для элемента.
Но не беспокойтесь! Вам не придется даже ссылаться на псевдонимы, вместо этого вы можете продолжать использовать реальное имя элемента в вашем коде. Oracle автоматически устанавливает соответствие между этим длинным именем и псевдонимом (именем в схеме), когда это нужно. Для получения полного или длинного имени для заданного короткого имени можно использовать функцию DBMS_JAVA.LONGNAME. Следующий запрос (хранимый в скрипте longname.sql) выводит длинные имена для всех Java- классов, описанных в текущей (той, к которой присоединены) схеме базы данных, у которых не совпадают краткое и полное имена:
SELECT object_name shortname,
DBMS_JAVA.LONGNAME (object_name) longname
FROM USER_OBJECTS
WHERE object_type = 'JAVA CLASS'
AND object_name != DBMS_JAVA.LONGNAME (object_name);
(Этот запрос также содержится в файле myJava.pkg, в пакете myJava, использование которого показано ниже.) Предположим, вы объявили класс, с очень длинным именем, например:
public class
DropAnyObjectIdentifiedByTypeAndName {
Вы можете удостовериться в том, что Oracle создал собственное короткое имя, следующим образом:
SQL> exec myjava.showlongnames
Short Name | Long Name
___________________________
Short: /247421b0_DropAnyObjectIdentif
Long: DropAnyObjectIdentifiedByTypeAndName
Просмотр Java-элементов
После загрузки исходных кодов Java, классов и элементов ресурсов в базу данных, можно получить информацию об этих элементах из нескольких представлений словаря данных (см. таблицу).
Представления словаря данных, содержащие информацию о Java-объектах
Представления словаря данных |
Информация, содержащаяся в словаре |
USER_OBJECTS |
Основная информация об объектах, для типа JAVA SOURCE |
ALL_OBJECTS |
JAVA CLASS и JAVA RESOURCE |
DBA_OBJECTS |
|
USER_ERRORS |
Любые ошибки компиляции ваших объектов |
ALL_ERRORS |
|
DBA_ERRORS |
|
USER_SOURCE |
Исходный код ваших источников Java, только в случае использования команды CREATE JAVA SOURCE для создания Java-объектов схемы |
Вы можете писать запросы к этим представлениям, либо создавать программы, которые используют эту информацию различными способами. Листинг 1 содержит скрипт с запросом, показывающим все Java-связанные объекты в вашей схеме и результат выполнения этого запроса. Предложение WHERE отсекает те объекты, которые Oracle создал для управления Java-объектами. Файл myjava.pkg содержит пакетный вариант запроса, представленный в Листинге 1, который позволяет просматривать Java-объекты, используя следующий процедурный вызов:
SQL> exec myjava.showobjects
Следующая команда выводит список всех Java-элементов, имена которых начинаются на OE:
SQL> exec myjava.showobjects ('OE%')
Столбец object_name в представлении словаря данных user_objects содержит полные имена Java-объектов схемы, за исключением имен, которые длиннее 30 символов или содержат непереводимые символы из набора символов UNICODE. В этом случае в столбце object_name хранится короткое имя. Для преобразования коротких имен в полные можно использовать функцию longname из пакета утилит DBMS_JAVA (см следующий раздел).
Помните следующие характеристики Java-элементов схемы в базе данных:
При использовании команды loadjava для загрузки Java-элементов в базу данных, исходный код ваших Java-элементов не переносится, – в базу данных помещаются только такие файлы, как class, jar, zip, и java,.
При использовании команды CREATE JAVA , исходный код Java-элементов хранится в базе данных. Объекты типа JAVA SOURCE можно увидеть в представлении словаря данных USER_OBJECTS, а также в представлениях ALL_OBJECTS, DBA_OBJECTS для этого элемента. Для извлечения исходного кода, размещения его в структурах данных PL/SQL и отображения или изменения текста этого кода можно использовать процедуру DBMS_JAVA.EXPORT_SOURCE, как об этом сказано в следующем разделе.
Экспорт Java-ресурсов
Пакет Oracle DBMS_JAVA содержит процедуры для экспорта исходного кода, классов и ресурсов. Вообще говоря, эти процедуры можно использовать для экспорта Java-ресурсов в типы данных Oracle8i BLOB или CLOB.
Пример процедуры в Листинге 2 дает представление о том, каким образом можно экспортировать исходный код Java-объектов схемы. Можно использовать команду CREATE JAVA для создания исходных Java-объектов, как показано ниже:
CREATE OR REPLACE JAVA SOURCE NAMED "Hello"
AS
public class Hello {
public static String hello() {
return "Hello Oracle World";}
};
/
Если для функции пакета DBMS_OUTPUT установлена возможность вывода в текстовый буфер, то можно просмотреть исходный код, как показано ниже:
SQL> exec show_java_source ('Hello')
public class Hello {
public static String hello() {
return "Hello Oracle World";}
};
Класс нельзя экспортировать в CLOB, только в BLOB. Кроме того, внутреннее представление исходного кода использует формат UTF8, поэтому этот формат применяется также для хранения исходного кода в BLOB.
Предоставление возможности вывода данных для Java
Когда классы System.out и System.err выполняются внутри базы данных, они посылают свои выходные данные в текущий файл трассировки. Этот файл не слишком удобное хранилище, особенно если вам просто нужно проверить правильно ли работает ваш код. Пакет DBMS_JAVA содержит процедуру, которую можно вызывать, чтобы перенаправить выходные данные в текстовый буфер DBMS_OUTPUT, из которого данные автоматически выводятся на экран SQL*Plus. Вот синтаксис вызова этой процедуры:
PROCEDURE DBMS_JAVA.set_output (buffersize NUMBER);
А вот пример ее использования:
SET SERVEROUTPUT ON SIZE 1000000
CALL DBMS_JAVA.SET_OUTPUT (1000000);
Документация по взаимодействию между DBMS_JAVA и DBMS_OUTPUT очень скудная. С помощью тестирования я выявил следующие правила:
Минимальный (и заданный по умолчанию) размер буфера составляет всего лишь 2000 байт; максимальный – 1000000 байт. Можно указывать число, выходящее за эти пределы, при этом не будет возникать ошибка (за исключением действительно больших чисел); SQL*Plus будет просто игнорировать его.
Размер буфера, заданный командой SET SERVEROUTPUT, замещает размер, указанный в DBMS_JAVA.SET_OUTPUT. Другими словами, если вы укажете меньшее значение при вызове DBMS_JAVA, то оно будет проигнорировано SQL*Plus, и будет использоваться большее значение.
Если выходные данные в Java превышают размер буфера, вы не получите сообщение, которое появляется при использовании DBMS_OUTPUT, а именно:
ORU-10027: buffer overflow, limit of [] bytes
Но вместо этого выходные данные будут усечены до размера буфера, указанного в SQL*Plus по умолчанию, и выполнение кода продолжится.
Как и в случае с DBMS_OUTPUT, выходные данные, выводимые Java-вызовами, не будут отображены до тех пор, пока не закончится выполнение хранимой процедуры, через которую они вызываются.
Обработка исключений в Java
С одной стороны, архитектура обработки исключений в Java-очень похожа на обработку исключений в PL/SQL. В языке Java, вы возбуждаете (throw) исключение и затем перехватываете (catch) его. В языке PL/SQL, вы возбуждаете (raise) исключение и затем обрабатываете (handle) его.
С другой стороны, обработка исключений в Java гораздо более трудоемка. Java предлагает базовый класс, называемый Exception. Все исключения являются объектами, основанными на этом классе, или на классах, происходящих от (расширяющих) Exception. Вы можете передавать исключения как параметры и манипулировать ими точно также как объектами любого другого класса.
Когда при выполнении SQL-оператора из хранимого Java-метода возбуждается исключение, это исключение является объектом подкласса java.sql.SQLException. Этот класс содержит два метода, которые возвращают код сообщения об ошибке Oracle, - getErrorCode(), и само сообщение об ошибке - getMessage() .
Если хранимая процедура Java, вызываемая из SQL или PL/SQL, возбуждает исключение, которое не перехватывается виртуальной Java-машиной, то вызывающая программа получает исключение, возбужденное сообщением об ошибке Java. Таким образом, виртуальная Java-машина на сервере базы данных Oracle сообщает обо всех не перехваченных исключениях (включая исключения, не связанные с SQL).
Рассмотрим теперь различные способы обработки ошибок и результирующие выходные данные. Допустим, что вы создали класс, использующий JDBC, для удаления объектов базы данных (смотри Листинг 3; этот пример скопирован из документации Oracle). Эта строка перехватит и отобразит на экране любое SQL-исключение:
catch (SQLException e)
{System.err.println(e.getMessage());}
Вы можете встроить этот класс в процедуру PL/SQL следующим образом:
CREATE OR REPLACE PROCEDURE dropany (
tp IN VARCHAR2,
nm IN VARCHAR2
)
AS LANGUAGE JAVA NAME 'DropAny.object (
java.lang.String,
java.lang.String)';
/
Листинг 4 показывает два результата, один из которых получается при попытке удалить несуществующий объект. Результаты, представленные в файле, напоминают о том, что вывод из System.err.println не появится на экране до тех пор, пока это не будет разрешено явным образом, путем вызова DBMS_JAVA.SET_OUTPUT. В любом случае, однако, исключение не будет распространяться на вызывающий блок, поскольку оно перехватывается внутри Java. После второго вызова процедуры dropany, можно увидеть, что сообщение об ошибке, переданное из метода getMessage(), является ошибкой Oracle.
Если закомментировать строки try и catch в методе DropAny.obj, то результат выполнения будет совершенно другим, как показано в Листинге 5. Здесь требуется небольшое пояснение. Все что написано между
java.sql.SQLException: ORA-00942: table or view does not exist
и
-29532
представляет собой дамп стека ошибок, генерируемый Java, и направленный на стандартный вывод, вне зависимости от того, как обрабатываются ошибки в PL/SQL. Другими словами, даже если раздел исключений выглядит следующим образом:
EXCEPTION WHEN OTHERS THEN NULL;
все эти сообщения все равно будут выведены на экран. Обработка в другом блоке (если он есть) будет затем продолжена. Последние три строки отображаемого вывода генерируются вызовом DBMS_OUTPUT.PUT_LINE.
Заметим, что возникает не ошибка Oracle ORA-00942, а общая Java-ошибка ORA-29532. Это действительно проблема. Как обнаружить при перехвате ошибки, что именно является настоящей ошибкой? Похоже, настал черед Человека, Пишущего Утилиты (Write-a-Utility Man)!
Я обратил внимание, что сообщение об ошибке, возвращаемое функцией SQLERRM, имеет следующий вид:
ORA-29532: Java call ...:
java.sql.SQLException: ORA-NNNNN ...
Следовательно, можно проверять наличие слова java.sql.SQLException и затем использовать функцию SUBSTR. Листинг 6 содержит процедуру, созданную с целью компенсации неудобного формата сообщений об ошибках Java. Процедура возвращает код ошибки и сообщение для текущей ошибки. Блок кода в Листинге 7 демонстрирует, каким образом можно использовать эту процедуру (процедура использует пакет log81, созданный файлом log81.pkg, чтобы записать информацию об ошибке в журнал).
Хотя эта процедура хранит информацию об ошибках в журнальных таблицах базы данных, она по-прежнему возвращает клиентской сессии стек исключений Java. Например, при запуске скрипта в SQL*Plus, стек исключений Java будет отображаться на экране.
Легкость интеграции
Темы, охваченные в этой статье, рассматривают некоторые приемы и основные принципы, используемые при встраивании функциональных возможностей Java в существующее Oracle PL/SQL окружение. Хотя магазины, работающие с базами данных Oracle, вероятно, не будут в ближайшем будущем переводить оптовую торговлю на Java, многие из нас захотят использовать Java как можно скорее. Информация, приведенная в этой статье, должна помочь вам сделать это рационально и эффективно.
Листинг 1. Java-связанные объекты в схеме
Листинг 1
Этот запрос показывает все Java-связанные объекты в вашей схеме :
COLUMN object_name FORMAT A30
SELECT object_name, object_type, status, timestamp
FROM user_objects
WHERE (object_name NOT LIKE 'SYS_%'
AND object_name NOT LIKE 'CREATE$%'
AND object_name NOT LIKE 'JAVA$%'
AND object_name NOT LIKE 'LOADLOB%')
AND object_type LIKE 'JAVA %'
ORDER BY object_type, object_name;
Вот результат выполнения этого запроса:
SQL> @showjava
OBJECT_NAME OBJECT_TYPE STATUS TIMESTAMP
___________ ____________ _______ ____________
Hello JAVA CLASS VALID 1999-05-19:16:42:27
JFile2 JAVA CLASS VALID 1999-05-26:17:07:11
JFile3 JAVA CLASS VALID 1999-05-27:12:53:46
plsolutions/java/putLn JAVA SOURCE VALID 1999-05-19:16:30:29
Листинг 2. Процедура экспорта из пакета Oracle DBMS_JAVA
Листинг 2
Процедура экспорта из пакета Oracle DBMS_JAVA позволяет извлекать
исходный код Java-объектов схемы.
/* showjava.sp */
CREATE OR REPLACE PROCEDURE show_java_source (
name IN VARCHAR2,
schema IN VARCHAR2 := NULL
)
IS
b CLOB;
v VARCHAR2(2000) ;
i INTEGER ;
BEGIN
/* Поместить исходный код Java в CLOB. */
DBMS_LOB.CREATETEMPORARY (b, FALSE);
DBMS_JAVA.EXPORT_SOURCE (name, NVL (schema, USER), b);
/* Прочитать CLOB в переменную типа VARCHAR2 и отобразить ее. */
i := 1000;
DBMS_LOB.READ (b, i, 1, v);
pl (v); /* запустить pl.sp для создания этой процедуры */
END;
/
Листниг 3. Создание класса для удаления объектов из базы
Листниг 3
Вы можете создать класс, использующий JDBC, для удаления объектов из базы данных Oracle.
/* dropany.java */
import java.sql.*;
import java.io.*;
import oracle.jdbc.driver.*;
public class DropAny {
public static void object (String object_type, String object_name)
throws SQLException {
// Присоединение к базе данных Oracle с помощью драйвера JDBC
Connection conn = new OracleDriver().defaultConnection();
// Построение SQL предложения
String sql = "DROP " + object_type + " " + object_name;
try {
Statement stmt = conn.createStatement();
stmt.executeUpdate(sql);
stmt.close();
} catch (SQLException e) {System.err.println(e.getMessage());}
}
}
Листинг 4. Попытка удаления несуществующего объекта
При попытке удаления несуществующего объекта, вы получите следующие результаты:
SQL> CONNECT scott/tiger
Connected.
SQL> SET SERVEROUTPUT ON
SQL> BEGIN dropany ('TABLE', 'blip'); END;
/
PL/SQL procedure successfully completed.
SQL> CALL DBMS_JAVA.SET_OUTPUT (1000000);
Call completed.
SQL> BEGIN dropany ('TABLE', 'blip'); END;
/
ORA-00942: table or view does not exist
|