Oracle PL/SQL Programming Guide to Oracle 8i Features

Oracle PL/SQL Programming Guide to Oracle 8i FeaturesSearch this book
Previous: 9.7 Using DBMS_JAVA and DBMS_JAVA_TESTChapter 9
Calling Java from PL/SQL
Next: 9.9 Examples
 

9.8 Publishing and Using Java in PL/SQL

Once you have written your Java classes and loaded them into the Oracle RDBMS, you can call their methods from within PL/SQL (and SQL) -- but only after you "publish" those methods via a PL/SQL wrapper.

9.8.1 Call Specs

You only need to build wrappers in PL/SQL for those Java methods you want to make available through a PL/SQL interface. Java methods can access other Java methods in the Java Virtual Machine directly, without any need for a wrapper. To publish a Java method, you write a call spec -- a PL/SQL program header (function or procedure) whose body is actually a call to a Java method via the LANGUAGE JAVA clause. This clause contains the following information about the Java method: its full name, its parameter types, and its return type. You can define these call specs as standalone functions or procedures, as programs within a package, and as methods in an object type:

CREATE [OR REPLACE] --Only if a standalone program
<Standard PL/SQL procedure/function header>
{IS | AS} LANGUAGE JAVA
NAME 'method_fullname (java_type_fullname[, java_type_fullname]...)
  [return java_type_fullname]';

Where java_type_fullname is the full name of the Java type, such as java.lang.String.

The NAME clause string identifies uniquely the Java method being wrapped. The fully qualified Java names and the call spec parameters, which are mapped by position only, must correspond, one to one, with the parameters in the program. If the Java method takes no arguments, code an empty parameter list for it but not for the function or procedure.

Here are a few examples:

CREATE OR REPLACE FUNCTION fDelete (
   file IN VARCHAR2) 
   RETURN NUMBER
AS LANGUAGE JAVA
   NAME 'JDelete.delete (
            java.lang.String) 
            return int';
CREATE OR REPLACE PACKAGE nat_health_care
IS
   PROCEDURE consolidate_insurer (ins Insurer) 
      AS LANGUAGE JAVA
      NAME 'NHC_consolidation.process(oracle.sql.STRUCT)';
END nat_health_care;
CREATE TYPE WarCriminal AS OBJECT (
  name VARCHAR2(100),
  victim_count NUMBER,
  MEMBER FUNCTION sentencing_date (
    name_in IN VARCHAR2) RETURN DATE
    AS LANGUAGE JAVA
    NAME 'warCriminal.dos (java.lang.String) 
             return java.sql.Timestamp'

9.8.2 Some Rules for Java Wrappers

Note the following rules for Java wrappers:

TIP: A function that declares OUT or IN OUT parameters cannot be called from SQL DML statements.

9.8.3 Mapping Datatypes

Earlier, I showed you one very simple example of a PL/SQL wrapper. That delete function passed a VARCHAR2 value to a java.lang.String parameter. The Java method returned an int, which was then passed back through the RETURN NUMBER clause of the PL/SQL function. Those are two straightforward examples of datatype mapping, that is, setting up a correspondence between a PL/SQL datatype and a Java datatype.

When you build a PL/SQL call spec, the PL/SQL and Java parameters, as well as the function result, are related by position and must have compatible datatypes. Table 9.6 lists all the datatype mappings currently allowed between PL/SQL and Java. If you rely on a supported datatype mapping, Oracle will convert from one to the other automatically.

As you can see with a quick glance at the mapping table, Oracle supports only automatic conversion for SQL datatypes. PL/SQL-specific datatypes, including BINARY_INTEGER, PLS_INTEGER, BOOLEAN, and index-by table types, are not supported. In those cases, you will have to perform manual conversion steps to transfer data between these two execution environments. See Section 9.9 for examples of nondefault mappings; see Oracle documentation for even more detailed examples involving the use of JDBC.


Table 9.6: Legal Datatype Mappings

SQL Type

Java Class

CHAR, NCHAR, LONG, VARCHAR2, NVARCHAR2

oracle.sql.CHAR 
java.lang.String 
java.sql.Date 
java.sql.Time 
java.sql.Timestamp 
java.lang.Byte 
java.lang.Short 
java.lang.Integer 
java.lang.Long 
java.lang.Float 
java.lang.Double 
java.math.BigDecimal 
byte, short, int, long, float, double 

DATE

oracle.sql.DATE 
java.sql.Date 
java.sql.Time 
java.sql.Timestamp 
java.lang.String 

NUMBER

oracle.sql.NUMBER 
java.lang.Byte 
java.lang.Short 
java.lang.Integer 
java.lang.Long 
java.lang.Float 
java.lang.Double 
java.math.BigDecimal 
byte, short, int, long, float, double 

RAW, LONG RAW

oracle.sql.RAW 
byte[] 

ROWID

oracle.sql.CHAR 
oracle.sql.ROWID 
java.lang.String 

BFILE

oracle.sql.BFILE 

BLOB

oracle.sql.BLOB 
oracle.jdbc2.Blob 

CLOB, NCLOB

oracle.sql.CLOB 
oracle.jdbc2.Clob 

OBJECT

oracle.sql.STRUCT 
oracle.SqljData 
oracle.jdbc2.Struct 

REF

oracle.sql.REF 
oracle.jdbc2.Ref 

TABLE, VARRAY

oracle.sql.ARRAY 
oracle.jdbc2.Array 

Any of the above SQL types

oracle.sql.CustomDatum 
oracle.sql.Datum 

9.8.4 Calling a Java Method in SQL

You can call PL/SQL functions of your own creation from within SQL DML statements. You can also call Java methods wrapped in PL/SQL from within SQL. However, these methods must conform to the following purity rules:

The objective of these restrictions is to control side effects that might disrupt your SQL statements. If you try to execute a SQL statement that calls a method violating any of these rules, you will receive a runtime error when the SQL statement is parsed.

It is also possible to call Java from PL/SQL via the SQL layer using the CALL command syntax and native dynamic SQL, as shown in the following code (the implementation of dropany is shown in the next section):

DECLARE
   Tp varchar2(30):='TABLE';
   Nm varchar2(30):='mytable';
BEGIN
   EXECUTE IMMEDIATE 'CALL dropany(:tp,:nm)' USING tp, nm;
END;

9.8.5 Exception Handling with Java

On the one hand, the Java exception handling architecture is very similar to that of PL/SQL. In Java-speak, you throw an exception and then catch it. In PL/SQL-speak, you raise an exception and then handle it.

On the other hand, exception handling in Java is much more robust. Java offers a foundation class called Exception. All exceptions are objects based on that class, or on classes derived from (extending) Exception. You can pass exceptions as parameters and manipulate them pretty much as you would objects of any other class.

When a Java stored method executes a SQL statement and an exception is thrown, then that exception is an object from a subclass of java.sql.SQLException. That class contains two methods that return the Oracle error code and error message: getErrorCode( ) and getMessage( ).

If a Java stored procedure called from SQL or PL/SQL throws an exception that is not caught by the JVM, the caller gets an exception thrown from a Java error message. This is how all uncaught exceptions (including non-SQL exceptions) are reported. Let's take a look at different ways of handling errors and the resulting output.

Suppose that I create a class that relies on JDBC to drop objects in the database (this is drawn from an example in Oracle documentation):

/* Filename on companion disk: 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 {
    // Connect to Oracle using JDBC driver
    Connection conn = new OracleDriver().defaultConnection();
    // Build SQL statement
    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());}
  }
}

TIP: Of course, it doesn't really make any sense to rely on JDBC to perform a drop object action, since this can be done much more easily in native PL/SQL. On the other hand, building it in Java does make the functionality available to other Java programs.

This version traps and displays any SQLException with this line:

} catch (SQLException e) {System.err.println(e.getMessage());}

I wrap this class inside a PL/SQL procedure as follows:

CREATE OR REPLACE PROCEDURE dropany (
   tp IN VARCHAR2,
   nm IN VARCHAR2
   )
AS LANGUAGE JAVA
   NAME 'DropAny.object (
            java.lang.String,
            java.lang.String)';
/

When I attempt to drop a nonexistent object, I will see one of the following two outcomes:

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

What you are seeing in these examples is a reminder that output from System.err.println will not appear on your screen until you explicitly enable it with a call to DBMS_ JAVA.SET_OUTPUT. In either case, however, no exception was raised back to the calling block, since it was caught inside Java. After the second call to dropany, you can see that the error message supplied through the getMessage( ) method is one taken directly from Oracle.

If I comment out the try and catch lines in the DropAny.obj method, I will get very different behavior, as shown:

SQL> BEGIN  
  2     dropany ('TABLE', 'blip');
  3  EXCEPTION
  4     WHEN OTHERS
  5     THEN
  6        DBMS_OUTPUT.PUT_LINE (SQLCODE);
  7        DBMS_OUTPUT.PUT_LINE (SQLERRM);
  8  END;
  9  /
java.sql.SQLException: ORA-00942: table or view does not exist
  at oracle.jdbc.kprb.KprbDBAccess.check_error(KprbDBAccess.java)
  at oracle.jdbc.kprb.KprbDBAccess.parseExecuteFetch(KprbDBAccess.java)
  at oracle.jdbc.driver.OracleStatement.doExecuteOther(OracleStatement.java)
  at oracle.jdbc.driver.OracleStatement.doExecuteWithBatch(OracleStatement.java)
  at oracle.jdbc.driver.OracleStatement.doExecute(OracleStatement.java)
  at oracle.jdbc.driver.OracleStatement.doExecuteWithTimeout(OracleStatement.java)
  at oracle.jdbc.driver.OracleStatement.executeUpdate(OracleStatement.java)
  at DropAny.object(DropAny.java:14)

-29532
ORA-29532: Java call terminated by uncaught Java exception: java.sql.SQLException: ORA-00942: table or view does not exist

This takes a little explaining. Everything between:

java.sql.SQLException: ORA-00942: table or view does not exist

and

-29532

represents an error stack dump generated by Java and sent to standard output, regardless of how you handle the error in PL/SQL. In other words, even if my exception section looked like this:

EXCEPTION WHEN OTHERS THEN NULL;

I would still get all that output to the screen, and then processing in the outer block (if any) would continue. The last three lines of output displayed are generated by the calls to DBMS_OUTPUT.PUT_LINE. Notice that the Oracle error is not ORA-00942, but instead is ORA-29532, a generic Java error. This is a problem. If you trap the error, how can you discover what the real error is? Looks like it's time for Write-A-Utility Man!

It appears to me that the error returned by SQLERRM is of this form:

ORA-29532: Java call ...: java.sql.SQLException: ORA-NNNNN ...

So I can scan for the presence of "java.sql.SQLException" and then SUBSTR from there. Here is a procedure that returns the error code and message for the current error, building in the smarts to compensate for the Java error message format:

/* Filename on companion disk: getErrorInfo.sp */
CREATE OR REPLACE PROCEDURE getErrorInfo (
   errcode OUT INTEGER,
   errtext OUT VARCHAR2)
IS
   c_keyword CONSTANT CHAR(23) := 'java.sql.SQLException: ';
   c_keyword_len CONSTANT PLS_INTEGER := 23;
   v_keyword_loc PLS_INTEGER;
   v_msg VARCHAR2(1000) := SQLERRM;
BEGIN
   v_keyword_loc := INSTR (v_msg, c_keyword);
   IF v_keyword_loc = 0
   THEN
      errcode := SQLCODE;
      errtext := SQLERRM;
   ELSE
      errtext := SUBSTR (
         v_msg, v_keyword_loc + c_keyword_len);
      errcode := 
         SUBSTR (errtext, 4, 6 /* ORA-NNNNN */);
   END IF;
END;
/   

The following block demonstrates how I might use this procedure (it relies on the log81 package, created by the log81.pkg file, to write the error information to the log):

/* Filename on companion disk: dropany2.tst */
BEGIN  
   dropany ('TABLE', 'blip');
EXCEPTION
   WHEN OTHERS
   THEN 
      DECLARE  
         v_errcode PLS_INTEGER;
         v_errtext VARCHAR2(1000);
      BEGIN
         getErrorInfo (v_errcode, v_errtext);
         log81.saveline (v_errcode, v_errtext);
      END;
END;
/

TIP: Even though I am saving error information to the database log table, the Java exception stack will still be returned to the host session. If, for example, I were running the script in SQL*Plus, the Java exception stack would be displayed on the screen.


Previous: 9.7 Using DBMS_JAVA and DBMS_JAVA_TESTOracle PL/SQL Programming Guide to Oracle 8i FeaturesNext: 9.9 Examples
9.7 Using DBMS_JAVA and DBMS_JAVA_TESTBook Index9.9 Examples

The Oracle Library Navigation

Copyright (c) 2000 O'Reilly & Associates. All rights reserved.

Library Home Oracle PL/SQL Programming, 2nd. Ed. Guide to Oracle 8i Features Oracle Built-in Packages Advanced PL/SQL Programming with Packages Oracle Web Applications Oracle PL/SQL Language Pocket Reference Oracle PL/SQL Built-ins Pocket Reference