The most common language within the Oracle database is still PL/SQL. This is the prefered language to develop Stored Procedures, Packages or Triggers. In order to develop with Java, the Java sources must be uploaded into the database. From this, the classes are extracted and displayed in the Data Dictionary as a schema object. It allows Java sources (.java) and class files (.class) or archives (.jar, .zip) to be uploaded.
Fig 1: Java Resourcen within the Oracle database. Origin: Oracle Java Developers Guide
The development IDE "PL/SQL Developer" also provides the ability to edit and compile Java Sources directly. These must be no longer uploaded cumbersome. Oracle points out, however, that the integrated JVM is not focused on development and that it is recommended, better to use external tools such as JDeveloper (Oracle) or Eclipse. In order to interact with the database (and the included tables), there is a server-side JDBC driver and SQLJ preprocessor integrated within the Oracle JVM.
Wrapper
To use the Java classes and methods as a stored procedure, only a small PL/SQL wrapper function must be written, which is responsible for communication with the Java method. Typically, such a wrapper is not more than 2-3 lines long. In the following example, a small Java method is developed, which returns a string. This is encapsulated by wrappers and can be used anywhere within the database.
The hello method of the JavaOracleDemo Class, takes a string as a parameter and returns it with the prefix "Hello".
1 | public class JavaOracleDemo { |
3 | public static String hello (String name) { |
4 | return ( "Hello " + name); |
PL/SQL Wrapper Package:
1 | create or replace package DEMO_JAVA_WRAPPER is |
3 | function hello(p_name in varchar2) return varchar2 as |
4 | language java name 'JavaOracleDemo.hello(java.lang.String) return java.lang.String' ; |
In the end we can use this method everywhere in the Oracle database even in a plain SQL statement:
Session Handling
Each database session which uses a Java class, creates a seperate Java session with it's own JVM, memory management and garbage collection. See Fig 2.
Fig 2: Database & Java Session Handling. Origin: Oracle Java Developers Guide
Passing Parameters
Passing parameters between Oracle (or the PL / SQL wrapper) and Java is sometimes a bit complicated. Scalar data types can be mapped very easy, as can be seen in the table below. So, for example, a VARCHAR2 in PL/SQL directly corresponds to a String class in Java. But when it comes to complex structures (eg, records or collections), the parameters can not simply pass. For those requirements, there is the Oracle tool "JPublisher", to which I will not go into more detail. For more information see the JPublisher Users Guide (Appendix). In the next chapter I will show a simple method to pass complex parameters (like records or collections) without using the JPublisher. I will explain how to manage this just with plain Oracle features.
Overview of Parameter Mappings
CHAR , CHARACTER , LONG , STRING , VARCHAR , VARCHAR2
| oracle.sql.CHAR
| java.lang.String
|
---|
RAW , LONG RAW
| oracle.sql.RAW
| byte[]
|
---|
BINARY_INTEGER , NATURAL , NATURALN , PLS_INTEGER , POSITIVE , POSITIVEN , SIGNTYPE , INT , INTEGER
| oracle.sql.NUMBER
| int
|
---|
DEC , DECIMAL , NUMBER , NUMERIC
| oracle.sql.NUMBER
| java.math.BigDecimal
|
---|
DOUBLE PRECISION , FLOAT
| oracle.sql.NUMBER
| double
|
---|
SMALLINT
| oracle.sql.NUMBER
| int
|
---|
REAL
| oracle.sql.NUMBER
| float
|
---|
DATE
| oracle.sql.DATE
| java.sql.Timestamp
|
---|
TIMESTAMP
TIMESTAMP WITH TZ
TIMESTAMP WITH LOCAL TZ
| oracle.sql.TIMESTAMP
oracle.sql.TIMESTAMPTZ
oracle.sql.TIMESTAMPLTZ
| java.sql.Timestamp
|
---|
ROWID , UROWID
| oracle.sql.ROWID
| oracle.sql.ROWID
|
---|
BOOLEAN
| boolean
| boolean
|
---|
CLOB
| oracle.sql.CLOB
| java.sql.Clob
|
---|
BLOB
| oracle.sql.BLOB
| java.sql.Blob
|
---|
BFILE
| oracle.sql.BFILE
| oracle.sql.BFILE
|
---|
Passing complex Parameters
To pass complex parameters as like records or collections, one alternative is, to store them into a temporary table like a stack. Before we call the Java method, we put the data on the stack and within the Java method we grab the data back. To pass the parameters back to PL/SQL, we simply do it vice versa. The only thing we need is a unique session-id to uniquely identify the parameters.
Example:
In the following Example, a Java method will get a collection of strings which will be returned with slightly modifications. Therefor we use a general parameter table (sys_java2plsql_para_tb
) as a stack, which will be used within Java and PL/SQL to access the data (get and set).
Definition of the Java Class
01 | import java.util.ArrayList; |
06 | #sql iterator StackIterator (String field1); |
08 | public class JavaOracleDemo { |
10 | public static void getSetStackDemo(Integer sessionId) throws Exception { |
13 | ArrayList stack = new ArrayList(); |
16 | StackIterator stackIterator; |
18 | #sql stackIterator = {select field1 from sys_java2plsql_para_tb where session_id = :sessionId }; |
21 | while (stackIterator.next()) { |
22 | String stackEntry = stackIterator.field1(); |
24 | stack.add(stackEntry); |
28 | #sql { delete from sys_java2plsql_para_tb where session_id = :sessionId }; |
31 | for (String stackEntry : stack) { |
32 | String stackEntryModified = stackEntry + " Java was here" ; |
33 | #sql { insert into SYS_JAVA2PLSQL_PARA_TB (session_id, field1) values |
34 | (:sessionId, :stackEntryModified) }; |
Definition of the PL/SQL Functions
If we have complex parameters to pass, we should have two PL/SQL functions. First, we have the simple wrapper for the 1:1 communication with the corresponding Java method. This function just have the session-ID as the only parameter and will never used directly. The second function is to hide the complexity of the parameter passing. It gets the collection of VARCHAR2 and stores each string on the stack. In addition this function will determine the unique session-ID for a proper communication with the Java method. This session-ID will be passed to the simple wrapper function.
02 | procedure getSetStackDemoW(p_session_id in number) as |
03 | language java name 'JavaOracleDemo.getSetStackDemo(java.lang.Integer) ' ; |
05 | procedure getSetStackDemo(p_string_tab in out t_tab) is |
06 | PRAGMA AUTONOMOUS_TRANSACTION; |
11 | cursor c_stack(ci_session_id in number) is |
13 | from sys_java2plsql_para_tb a |
14 | where a.session_id = ci_session_id; |
18 | l_session_id := common_seq.nextval; |
21 | for i in 1 .. p_string_tab. count loop |
22 | insert into sys_java2plsql_para_tb |
25 | (l_session_id, p_string_tab(i)); |
29 | getSetStackDemoW(l_session_id); |
32 | for c1 in c_stack(l_session_id) loop |
34 | p_string_tab(idx) := c1.field1; |
The following anonymous PL/SQL program shows how to use ...
02 | l_strings demo_java_wrapper.t_tab; |
04 | l_strings(1) := 'Hello' ; |
05 | l_strings(2) := 'World' ; |
06 | l_strings(3) := 'Oracle' ; |
08 | DEMO_JAVA_WRAPPER.getSetStackDemo (l_strings); |
10 | for i in 1..l_strings. count loop |
11 | dbms_output.put_line (l_strings(i)); |
... and this is the expected output:
Access the FileSystem with Java
Oracle already provides with the package UTL_FILE a few simple functions to access OS File System for reading or writing files. But if you want to read the content of a whole directory, it may become difficult. This is perfect for Java. Here we have all capabilities and all the comfort, for which Java is known for. |
Requirements
To enable Java, to access the FileSystem, we need certain permissions (aka Java Policies). It is important to restart the Database, after setting those permissions. It took me hours to figure out, why the hell Java was prohibited to read the content of the Temp-Directory. The next day, I was wondering what the problem could be, but before I spent more time in investigations, I checked the function again. And behold, it was working. The magic thing was, that the Database was rebooted over night. Lesson learned!
To check which permissions already has been set, use the following SQL:
select * from USER_JAVA_POLICY
where grantee_name = '<Schema-Name>'
|
The following permissions should be set: (execute as SYS)
3 | dbms_java.grant_permission( '' , 'java.io.FilePermission' , '<<ALL FILES>>' , 'read,write,execute,delete' ); |
4 | dbms_java.grant_permission( '' , 'java.io.FilePermission' , 'C:\temp' , 'read' ); |
5 | dbms_java.grant_permission( '' , 'java.lang.RuntimePermission' , '*' , 'writeFileDescriptor' ); |
6 | dbms_java.grant_permission( '' , 'java.net.SocketPermission' , '*' , 'connect, resolve' ); |
7 | dbms_java.grant_permission( '' , 'java.util.PropertyPermission' , '*' , 'read' ); |
In addition, the following well known ACL permissions must be set: (execute as SYS)
03 | DBMS_NETWORK_ACL_ADMIN.CREATE_ACL(acl => 'dbserver.xml' , |
04 | description => 'DB Server ACL' , |
05 | principal => 'PUBLIC' , |
07 | privilege => 'connect' ); |
08 | DBMS_NETWORK_ACL_ADMIN.ADD_PRIVILEGE(acl => 'dbserver.xml' , |
09 | principal => 'PUBLIC' , |
11 | privilege => 'resolve' ); |
12 | DBMS_NETWORK_ACL_ADMIN.ASSIGN_ACL(acl => 'dbserver.xml' , |
The Java Method to read a Directory
It is quite easy to read the content of a directory with Java. To put a bit more sense in this example, the Java method provides the functionality to filter the files with regular expressions. The challenge is to pass the results back to the PL/SQL-Function. But in the chapter above, I described a method to do that in a very easy way. In the download area, you will find a zip archive with the entire example or simply click here.
03 | import java.io.FileInputStream; |
08 | * @author Stefan Armbruster |
11 | public class FileUtils { |
13 | public static File[] getDirectoryList(String directoryPath, String filterPattern) throws Exception { |
15 | File[] fileList = null ; |
16 | FilenameFilter filter; |
18 | directory = new File(directoryPath); |
21 | if (filterPattern != null ) { |
22 | filter = new FileFilter(filterPattern); |
23 | fileList = directory.listFiles(filter); |
25 | fileList = directory.listFiles(); |
27 | } catch (Exception ex) { |
28 | throw new Exception(ex.getLocalizedMessage()); |
After that, we need a simple Java Wrapper Function, to encapsulate the parameter passing. The Wrapper-Method has three parameters:
- The Directory itself
- The regular expression
- The session-ID to synchronize the result with the PL/SQL function.
01 | package starit.wrapper; |
04 | import starit.utils.FileUtils; |
07 | * Wrapper Class to access File-Utils |
08 | * Autor: Stefan Armbruster, 12.2014 |
12 | public class OWFileUtils { |
13 | public static void OWgetDirectoryList(String directoryPath, |
14 | String filterPattern, Integer sessionId) throws Exception { |
15 | File[] fileList = null ; |
19 | String accessType = "DIRECTORY_LIST" ; |
22 | fileList = FileUtils.getDirectoryList(directoryPath, filterPattern); |
25 | for ( int i = 0 ; i < fileList.length; i++) { |
26 | if (fileList[i].isDirectory()) { |
31 | fileName = fileList[i].getName(); |
32 | fileSize = fileList[i].length(); |
35 | #sql { insert into SYS_JAVA2PLSQL_PARA_TB (session_id, access_type, field1, field2, field3, field4) values |
36 | (:sessionId, :accessType, :directoryPath, :fileName, :fileType, :fileSize) |
Finally we need two PL/SQL Functions to invoke Java. First, the PL/SQL-Java Wrapper, for the core communication with Java. In this case, this wrapper function is just internal, because of the complexity of the parameter. Second, we need another PL/SQL function, which handles the parameter passing from Java to PL/SQL. This is the function we provide as an external interface, to use it everywhere within Oracle. To complete the example we should have two type definitions for the directory content.
01 | create or replace package body JAVA_WRAPPER is |
05 | procedure JWgetDirectoryList(p_directory in varchar2, |
07 | p_session_id in number) as |
08 | language java name 'starit.wrapper.OWFileUtils.OWgetDirectoryList(java.lang.String, java.lang.String, java.lang.Integer )' ; |
16 | function get_dir_list(p_directory in varchar2, p_filter in varchar2) |
17 | return o_tab_dir_list is |
18 | PRAGMA AUTONOMOUS_TRANSACTION; |
21 | l_rec_dir o_rec_dir_list_entry; |
22 | l_tab_dir o_tab_dir_list; |
25 | cursor c_data(ci_session_id in number) is |
27 | from SYS_JAVA2PLSQL_PARA_TB a |
28 | where a.session_id = ci_session_id; |
32 | l_rec_dir := o_rec_dir_list_entry( null , null , null ); |
33 | l_tab_dir := o_tab_dir_list(); |
35 | l_session_id := common_seq.nextval; |
38 | JWgetDirectoryList(p_directory, p_filter, l_session_id); |
41 | for c1 in c_data(l_session_id) loop |
43 | l_rec_dir.fileName := c1.field2; |
44 | l_rec_dir.fileType := c1.field3; |
45 | l_rec_dir.fileSize := to_number(c1.field4); |
48 | l_tab_dir(l_tab_dir. last ) := l_rec_dir; |
59 | raise_application_error(-20001, 'File not found' ); |
Pipelined Function
Now you can use the result of the get_dir_list function everywhere in PL/SQL. But because this function returns a collection, it is not possible to use it within a regular SQL statement. But exactly this is a functionality we would like to have and this requirement fits perfect for that. Therefor we use a Oracle technology as called Pipelined Functions. Each row within the collection will be returned sequentially to use it in a SQL statement.
02 | function get_dir_list_pipe(p_directory in varchar2) return o_tab_dir_list |
05 | l_rec_dir o_rec_dir_list_entry; |
06 | l_tab_dir o_tab_dir_list; |
10 | l_tab_dir := get_dir_list(p_directory, null ); |
12 | for i in 1 .. l_tab_dir. count loop |
13 | l_rec_dir := l_tab_dir(i); |
14 | pipe row(o_rec_dir_list_entry(l_rec_dir.fileName, |
Then, the content of the c:\temp directory will be showed like this:
Appendix
Definition of the Parameter Table
Documents and Links
Java Developers Guide for Oracle 11g (11.2)
JPublisher Users Guide
Pipelined Table Functions
Download the source code of the Directory Listing example right here.