Мне редко приходится писать на Java хранимые процедуры для СУБД Oracle. А когда приходится, всякий раз я трачу некоторое время на "восстановление контекста", чтобы вспомнить особенности встроенной JVM и работы с ней. Чтобы в следующий раз восстановление контекста прошло быстрее, в этой статье в конспективной форме я собрал самые необходимые сведения и примеры кода на Java для встроенной Oracle JVM.
Вот так можно посмотреть, установлена ли поддержка Java в СУБД Oracle:
SQL> SELECT * FROM all_registry_banners WHERE UPPER(banner) LIKE '%JAVA%';
BANNER
--------------------------------------------------------------------------------
JServer JAVA Virtual Machine Release 11.2.0.2.0 - Development
Oracle Database Java Packages Release 11.2.0.2.0 - Development
Запрос был выполнен на Oracle 11gR2 Enterprise Edition. Но какая же версия Java поддерживается?
SQL> CREATE OR REPLACE FUNCTION get_java_property (prop IN VARCHAR2)
2 RETURN VARCHAR2 IS LANGUAGE JAVA
3 name 'java.lang.System.getProperty(java.lang.String) return java.lang.String';
4 /
Function created
SQL> SELECT get_java_property('java.version') FROM dual;
GET_JAVA_PROPERTY('JAVA.VERSIO
--------------------------------------------------------------------------------
1.5.0_10
Кстати, в Oracle 11gR2 Express Edition встроенная JVM отсутствует:
SQL> SELECT * FROM all_registry_banners WHERE UPPER(banner) LIKE '%JAVA%';
BANNER
--------------------------------------------------------------------------------
Следующая таблица содержит ряд отличий стандартной JVM и встроенной в СУБД Oracle JVM.
Стандартная JVM | Встроенная Oracle JVM |
---|---|
Исполнямый код запускается в сеансе ОС командной строкой 'java <classname>' | Исполнямый код запускается в сеансе СУБД Oracle вызовом PL/SQL процедуры или функции - обертки метода Java. |
При запуске вызывается метод public static void main(String args[])
|
Может быть вызван любой из public static методов класса, опубликованный через PL/SQL процедуру или функцию - обертку.
|
JVM живет, пока выполняется 'java <classname>' | JVM живет на протяжении сеанса работы с СУБД Oracle. |
Исходный код, байт-код и ресурсы содержатся в файлах ОС (.java, .class, .properties ), компилируются и загружаются JVM из файлов ОС.
|
Исходный код, байт-код и ресурсы хранятся в БД как объекты схемы (JAVA SOURCE, JAVA CLASS, JAVA RESOURCE ), компилируются и загружаются для исполнения JVM из объектов схемы.
|
Пространство имен конструируется из разделенных точками имен пакетов, например, java.util.regex или ru.trofimov.helloworld
| Стандартное пространство имен Java вкладывается в схемы Oracle. Классы стандартной библиотеки Java находятся в схеме SYS и доступны в других схемах через публичные синонимы. |
Имя класса: ru.trofimov.helloworld.Hello
|
Имя класса (объекта схемы): ru/trofimov/helloworld/Hello См. примечание * внизу таблицы. |
Единственный CLASSPATH задает список директорий и jar-файлов, где JVM ищет классы для загрузки.
| Resolver, свой для каждого класса, задает список схем Oracle для поиска Java-объектов-схемы. |
Поддерживает графический интерфейс (GUI) и взаимодействие с пользователем посредством GUI. | Не поддерживает графический интерфейс (GUI) и взаимодействие с пользователем посредством GUI. |
Поддерживает воспроизведение и запись звука с помощью звуковых средств компьютера. | Не поддерживает воспроизведение и запись звука с помощью звуковых средств компьютера. |
Поддерживает эффективное параллельное выполнение задач с помощью Java потоков (threads). | Синтаксис Java потоков (threads) поддерживается, но распараллеливания потоков, на самом деле, не происходит. Эффективное распараллеливание в СУБД Oracle происходит на уровне пользовательских сеансов. |
Для работы с БД Oracle используется драйвер JDBC. | Для работы с БД Oracle используется так называемый Server-Side JDBC Internal Driver, взаимодействующий с БД минуя сетевой протокол и исключая накладные расходы. |
Опции компиляции задаются в командной строке компилятора javac .
|
Опции компиляции задаются в командной строке утилиты loadjava , либо в таблице JAVA$OPTIONS (при помощи пакета DBMS_JAVA ), причем опции loadjava приоритетнее сохраненных в таблице.
|
* Имя Java класса может быть до 4000 символов длиной, а имя объекта схемы данных - не более 30 символов. В связи с этим пакет dbms_java
предоставляет функции shortname(class_name)
и longname(object_name)
, которые возращают длинное и короткое имена Java классов, соответственно. Пример:
SQL> -- Java classes and their shortnames
SQL> SELECT dbms_java.shortname(c.name), c.name
2 FROM dba_java_classes c
3 WHERE rownum <= 3;
DBMS_JAVA.SHORTNAME(C.NAME) NAME
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
/1000323d_DelegateInvocationHa com/sun/corba/se/spi/orbutil/proxy/DelegateInvocationHandlerImpl$1
/1000e8d1_LinkedHashMapValueIt java/util/LinkedHashMap$ValueIterator
/1005bd30_LnkdConstant oracle/aurora/util/classfile/Lnkd$Constant
SQL> -- Java objects and their longnames
SQL> SELECT object_name, dbms_java.longname(object_name) longname, status
2 FROM all_objects
3 WHERE object_type IN ('JAVA CLASS')
4 AND rownum <= 3;
OBJECT_NAME LONGNAME STATUS
------------------------------ -------------------------------------------------------------------------------- -------
/1000323d_DelegateInvocationHa com/sun/corba/se/spi/orbutil/proxy/DelegateInvocationHandlerImpl$1 VALID
/1000e8d1_LinkedHashMapValueIt java/util/LinkedHashMap$ValueIterator VALID
/1005bd30_LnkdConstant oracle/aurora/util/classfile/Lnkd$Constant VALID
Теперь перейду к практике. Создам JAVA SOURCE, откомпилирую его в JAVA CLASS, опубликую и выполню.
SQL> -- the simplest start: hello world
SQL>
SQL> CREATE OR REPLACE AND COMPILE JAVA SOURCE NAMED "ru/trofimov/helloworld/Hello" AS
2 package ru.trofimov.helloworld;
3 public class Hello
4 {
5 public static void hello()
6 {
7 System.out.println("Hello world!");
8 }
9 }
10 /
Java created
Строки 2 - 9 содержат исходный код на Java. Найдем созданные объекты JAVA SOURCE и JAVA CLASS в системном словаре Oracle:
SQL> SELECT line, text
2 FROM dba_source
3 WHERE type='JAVA SOURCE'
4 AND name = 'ru/trofimov/helloworld/Hello'
5 ;
LINE TEXT
---------- --------------------------------------------------------------------------------
1 package ru.trofimov.helloworld;
2 public class Hello
3 {
4 public static void hello()
5 {
6 System.out.println("Hello world!");
7 }
8 }
9
10
10 rows selected
SQL> SELECT dbms_java.shortname(c.name), c.name
2 FROM dba_java_classes c
3 WHERE name = 'ru/trofimov/helloworld/Hello'
4 ;
DBMS_JAVA.SHORTNAME(C.NAME) NAME
---------------------------------------- ----------------------------------------
ru/trofimov/helloworld/Hello ru/trofimov/helloworld/Hello
Создадим PL/SQL процедуру-обертку для метода hello
класса Hello
и выполним ее:
SQL> CREATE OR REPLACE PROCEDURE hello AS LANGUAGE JAVA
2 NAME 'ru.trofimov.helloworld.Hello.hello()';
3 /
Procedure created
SQL> show error
No errors
SQL> set serveroutput on
SQL> call dbms_java.set_output(1000000);
Method called
SQL> BEGIN hello; END;
2 /
Hello world!
PL/SQL procedure successfully completed
По умолчанию вывод System.out.println()
идет в трейс-файлы в директории udump
. Вызов dbms_java.set_output
позволяет перенаправить вывод на консоль в рамках текущей сессии.
Продемонстрирую передачу аргумента методу Java:
SQL> -- calling java with argument(s)
SQL>
SQL> set define off
SQL>
SQL> CREATE OR REPLACE AND COMPILE JAVA SOURCE NAMED "Hello" AS
2 public class Hello
3 {
4 public static void p(String message) {
5 System.out.println(message);
6 }
7
8 public static void hello()
9 {
10 p("Hello world!");
11 }
12
13 public static void helloUser(String name)
14 {
15 p("Hello, " + (name != null && name != "" ? name : "user") + "!");
16 }
17 }
18 /
Java created
SQL> show error
No errors
SQL>
SQL> CREATE OR REPLACE PROCEDURE hello_user(username VARCHAR2) AS
2 LANGUAGE JAVA NAME 'Hello.helloUser(java.lang.String)';
3 /
Procedure created
SQL> show error
No errors
SQL> exec hello_user(null)
Hello, user!
PL/SQL procedure successfully completed
SQL> exec hello_user('Andrey')
Hello, Andrey!
PL/SQL procedure successfully completed
Кстати, можно найти метод Hello.helloUser
и его параметр в системном словаре с помощью следующих запросов:
SELECT *
FROM dba_java_methods
WHERE name = 'Hello'
AND method_name = 'helloUser';
SELECT *
FROM dba_java_arguments
WHERE name = 'Hello'
AND method_name = 'helloUser';
А теперь я хочу вернуть значение из метода Java в вызывающий PL/SQL код.
SQL> -- getting values from java
SQL>
SQL> CREATE OR REPLACE AND COMPILE JAVA SOURCE NAMED "Hello" AS
2 public class Hello
3 {
4 private static void p(String message)
5 {
6 System.out.println(message);
7 }
8
9 public static void hello()
10 {
11 p("Hello world!");
12 }
13
14 public static String helloString(String name)
15 {
16 return "Hello, " + (name != null && name != "" ? name : "user") + "!";
17 }
18
19 public static void helloUser(String name)
20 {
21 p(helloString(name));
22 }
23 }
24 /
Java created
SQL> show error
No errors
SQL>
SQL> CREATE OR REPLACE FUNCTION hello_string(username VARCHAR2) RETURN VARCHAR2
2 AS LANGUAGE JAVA
3 NAME 'Hello.helloString(java.lang.String) return java.lang.String';
4 /
Function created
SQL> show error
No errors
SQL> select hello_string('Andrey') from dual;
HELLO_STRING('ANDREY')
-----------------------------------------------------------
Hello, Andrey!
SQL> exec hello_user('Андрей')
Hello, Андрей!
PL/SQL procedure successfully completed
При передаче аргумента из PL/SQL в Java метод происходит автоматическое преобразование типа данных SQL в тип данных Java. А при возврате значения из Java метода в PL/SQL код происходит автоматическое преобразование из типа данных Java в тип данных SQL.
Приведу таблицу соответствий основных типов данных SQL и Java (Подробнее см. в Oracle Database Java Developer's Guide):
Тип данных SQL | Тип данных Java |
---|---|
CHAR, VARCHAR2
|
java.lang.String |
NUMBER
|
boolean |
BINARY_INTEGER
|
boolean |
DATE
|
oracle.sql.DATE
|
RAW
|
oracle.sql.RAW
|
BLOB
|
oracle.sql.BLOB
|
CLOB
|
oracle.sql.CLOB
|
BFILE
|
oracle.sql.BFILE
|
ROWID
|
oracle.sql.ROWID
|
TIMESTAMP
|
oracle.sql.TIMESTAMP
|
TIMESTAMP WITH TIME ZONE
|
oracle.sql.TIMESTAMPTZ
|
TIMESTAMP WITH LOCAL TIME ZONE
|
oracle.sql.TIMESTAMPLTZ
|
На этом пока закончу, устранив из базы данных следы своих экспериментов:
SQL> DROP FUNCTION hello_string;
Function dropped
SQL> DROP PROCEDURE hello_user;
Procedure dropped
SQL> DROP PROCEDURE hello;
Procedure dropped
SQL> DROP JAVA SOURCE "Hello";
Java dropped
SQL> DROP JAVA SOURCE "ru.trofimov.helloworld.Hello";
Java dropped
В следующий раз я собираюсь исследовать поведение статических переменных класса Java в контексте сеансов СУБД Oracle и поставить несколько других экспериментов с Java. А также разработать PL/SQL пакет-обертку для чтения и записи внешних файлов на базе стандартной библиотеки Java.
Здравствуйте, Андрей.
ОтветитьУдалитьПрошу уточнить пару моментов
1) Что имеется в виду под сеансом работы с СУБД? Сессия пользователя или транзакция? Или что то другое?
2) Насколько легко может быть "убит" экземпляр Oracle "неправильным" java кодом? Насколько использование java-ХП безопасно?
3) Какие правила нужно соблюдать для написания безопасных java-ХП?
Заранее спасибо
Добрый день, Sergius.
ОтветитьУдалить1) Сеанс работы пользователя = сессия пользователя.
2) Я не слышал, чтобы экземпляр Oracle был убит неправильным java-кодом. Использование хранимых процедур на java в Oracle не опасней, чем использование хранимых процедур на PL/SQL. (Хотя и те, и другие способны, например, заполнить файловую систему ненужными файлами или иначе навредить.)
3) Java и JVM изначально проектировались как безопасный язык и безопасная среда выполнения. О специальных правилах для написания безопасных хранимых процедур на Java мне не известно. Пишите на Java, а компилятор и JVM сделают все, чтобы не допустить фатальных вещей.