You have the ability to write and deploy Java libraries inside the database. However, you need to know that only the Oracle Enterprise and Standard Database Editions actually support an internal Java Virtual Machine (JVM).

These Java libraries work like external Java programs. While they don’t use the Java Database Connectivity (JDBC) model the same way as external and middle-tier Java programs, the difference is small.

The difference between internal and external Java libraries is how they connect with the Oracle database. External Java libraries connect through the Oracle listener. Internal Java libraries connect through an internal connection.

This article shows you how to write internal Java libraries that insert, update, and delete data. It also shows you how to deploy them. The article is divided into five sections:

  • Basic Architecture
  • Writing and Deploying Internal Java Libraries
  • Writing and Deploying Internal DML Java Libraries
  • Wrapping Java Libraries with PL/SQL
  • Creating and Using Test Cases

You can skip to the section of interest if you know the basic architecture. You find instructions and complete code examples to demonstrate techniques. However, they’re basic units to simply show you how and they do require a working knowledge of the Java programming language.

Basic Architecture

You write internal Java libraries like other Java programs. That means you can write them in JDeveloper, Netbeans, or a simple text editor. There is a twist on how you compile them because Oracle provides you with two options.

You can elect to compile them as Java class objects or embed them within SQL scripts. Oracle provides the loadjava and dropjava utilities when you opt to write and compile internal Java like standalone class files. Alternatively, Oracle’s SQL syntax supports an extension that let’s you compile Java from the SQL*Plus command line.

Regardless of which approach you choose, the Java classes become libraries inside the Oracle database. There is one key difference based on how you deploy the code. There is no clear text version in the data dictionary when you use the loadjava utility. However, there is a clear text version of the Java code when you use SQL to create Java libraries.

There’s no clear guidance from Oracle on whether you should or shouldn’t create clear text versions of your Java libraries. The best guidance is to follow the same practice you use when writing PL/SQL stored functions, procedures, and packages. If you write key business logic in PL/SQL that you want to protect, you wrap the PL/SQL code to make it unreadable. By the same token, compile externally and use the loadjava utility when you write key business logic in Java to protect it from prying eyes.  

Writing and Deploying Internal Java Libraries

It’s always best to start with small examples. This section does exactly that. It shows you how to compile and deploy a “Hello World” program with both techniques.

The first technique writes, compiles, and deploys a Java class file with the loadjava utility. This example uses a main() test method that let’s you test your class file outside of the database.

The HelloWorld.java file is:

// Class definition.

public class HelloWorld {

  public static String hello() {

    return "Hello World!"; }

 

  // Test method.

  public static void main(String args[]) {

    System.out.println(HelloWorld.hello()); }

}

You can test the hello() method of the HelloWorld class by calling the class file, which calls the main() test method, like

java HelloWorld

After testing the HelloWorld class file, you can use the loadjava utility to load the class file. The syntax is the following for a student schema:

loadjava -r -f -o -user student/student HelloWorld.class

You can use the following query to verify that you’ve successfully put the class into the database:

SQL> COLUMN "Java Library" FORMAT A15

SQL> SELECT object_name AS "Java Library"

  2  FROM   user_objects

  3  WHERE  object_name = 'HelloWorld';

If successful, it prints:

Java Library

---------------

HelloWorld

You need to write a PL/SQL function to wrap the Java library. The hello function is straightforward, like

SQL> CREATE OR REPLACE FUNCTION hello RETURN VARCHAR2 IS

  2  LANGUAGE JAVA

  3  NAME 'HelloWorld.hello() return java.lang.String';

  4  /

Line 3 maps the hello function to the internal HelloWorld class by calling the hello() method. You can then call the PL/SQL hello function, like this

SQL> COLUMN message FORMAT A20

SQL> SELECT hello AS message FROM dual;

It will print

MESSAGE

--------------------

Hello World!

You need to remove the HelloWorld.class before you can test the SQL*Plus method of deploying the class. You use the dropjava utility to remove the class file, like this:

dropjava -user student/student HelloWorld.class

Reconnecting as the student user, you can compile the same program from the SQL*Plus command line, like this:

SQL> CREATE OR REPLACE AND RESOLVE JAVA SOURCE

  2  NAMED HelloWorld AS

  3  // Class definition.

  4  public class HelloWorld {

  5    public static String hello() {

  6      return "Hello World!"; }

  7

  8    // Test method.

  9    public static void main(String args[]) {

 10      System.out.println(HelloWorld.hello()); }

 11  }

 12  /

The previously created PL/SQL hello function is still valid and the hello function maps to the new copy of the HelloWorld.class file. There is one twist to removing the HelloWorld.class file after you create it by using SQL*Plus. You can’t use the dropjava utility to remove it.

You would remove it with the following SQL command:

SQL> DROP JAVA SOURCE HELLOWORLD;

The change in how you remove the HelloWorld.class library occurs because when you create it with SQL*Plus it does two things. It parses and compiles the Java source and writes the copy of the Java source file code to the data dictionary. That’s why you need to use a DROP statement to remove the plain text copy and compiled class file.

Writing and Deploying Internal DML Java Libraries

This section shows you how to write and deploy several internal DML Java libraries. The easiest way to write and deploy Java that inserts, updates, and deletes data is to put them in as separate methods of a single class file.

The sql.class file contains three methods for testing procedures, three methods for testing functions, and one procedure to query a single row. The procedures take an optimistic approach toward processing because they return no acknowledgment of success or failure. The functions, on the other hand, return zero when successful and an Oracle error number when they fail.

The three methods for procedures are:

  • runInsert –    takes a film name parameter to insert
  • runUpdate –  takes an old and new film name parameter to change the film name
  • runDelete –    takes a film name to delete

The three methods for functions are:

  • execInsert –   takes a film name parameter to insert and returns zero for success or an Oracle error number for failure
  • execUpdate – takes an old and new film name parameter to change the film name and reuturns zero for success or an Oracle error number for failure
  • execDelete –  takes a film name to delete and returns zero for success or an Oracle error number for failure

The query method is:

  • query –           takes a film name to verify its entry and returns zero for success or an Oracle error number for failure

The sql.java source file for these DML libraries is:

// Oracle class imports.

import java.sql.*;

import oracle.jdbc.driver.*;

 

// Class definition.

public class sql {

 

  // Execute INSERT statement for a PL/SQL procedure.

  public static void runInsert(String filmName) throws SQLException {

    // Declare a local INSERT statement.

    String insertStatement = "INSERT INTO film (film_name) VALUES (?)";

 

    // Declare a connection.

    Connection conn =

      DriverManager.getConnection("jdbc:default:connection:");

     

      // Declare a connection and execute an INSERT statement.

      try {

      PreparedStatement ps = conn.prepareStatement(insertStatement);

      ps.setString(1,filmName);

      ps.execute();

        conn.close(); }

      catch (SQLException e) {}

    finally {

      if (conn != null) {

      conn.close(); }}

  }

 

  // Execute UPDATE statement for a PL/SQL procedure.

  public static void runUpdate(String oldFilmName, String newFilmName)

    throws SQLException {

    // Declare a local UPDATE statement.

    String updateStatement = "UPDATE film SET film_name = ? WHERE film_name = ?";

 

    // Declare a connection.

    Connection conn =

      DriverManager.getConnection("jdbc:default:connection:");

     

      // Declare a connection and execute an UPDATE statement.

      try {

      PreparedStatement ps = conn.prepareStatement(updateStatement);

      ps.setString(1,newFilmName);

        ps.setString(2,oldFilmName);

      ps.execute();

      conn.close(); }

      catch (SQLException e) {}

    finally {

      if (conn != null) {

      conn.close(); }}

  }

 

  // Execute DELETE statement for a PL/SQL procedure.

  public static void runDelete(String filmName) throws SQLException {

    // Declare a DELETE statement.

    String deleteStatement = "DELETE FROM film WHERE film_name = ?";

 

    // Declare a connection.

    Connection conn =

      DriverManager.getConnection("jdbc:default:connection:");

     

      // Declare a connection and execute a DELETE statement.

      try {

      PreparedStatement ps = conn.prepareStatement(deleteStatement);

      ps.setString(1,filmName);

      ps.execute();

      conn.close(); }

    catch (SQLException e) {}

    finally {

      if (conn != null) {

      conn.close(); }}

  }

 

  // Execute an INSERT statement for a PL/SQL function.    

  public static int execInsert(String filmName) throws SQLException {

    // Declare return code and INSERT statement variables.

      int returnCode = 0;

    String insertStatement = "INSERT INTO film (film_name) VALUES (?)";

 

    // Declare a connection.

    Connection conn =

      DriverManager.getConnection("jdbc:default:connection:");

     

      // Declare a connection and execute a INSERT statement.

      try {

      PreparedStatement ps = conn.prepareStatement(insertStatement);

      ps.setString(1,filmName);

      ps.execute();

        conn.close();

      returnCode = 0; }

      catch (SQLException e) {

      returnCode = e.getErrorCode(); } // Set error or return code.

    finally {

      if (conn != null) {

        conn.close(); }}

    // Return zero or error code.

      return returnCode;

  }

 

  // Execute UPDATE statement for a PL/SQL function.

  public static int execUpdate(String oldFilmName, String newFilmName)

    throws SQLException {

    // Declare return code and UPDATE statement variables.

      int returnCode = 0;

    String updateStatement = "UPDATE film SET film_name = ? WHERE film_name = ?";

 

    // Declare a connection.

    Connection conn =

      DriverManager.getConnection("jdbc:default:connection:");

     

      // Declare a connection and execute an UPDATE statement.

      try {

      PreparedStatement ps = conn.prepareStatement(updateStatement);

      ps.setString(1,newFilmName);

        ps.setString(2,oldFilmName);

      ps.execute();

      conn.close(); }

      catch (SQLException e) {

      returnCode = e.getErrorCode(); } // Set error or return code.

    finally {

      if (conn != null) {

        conn.close(); }}

    // Return zero or error code.

      return returnCode;

  }

 

  // Execute DELETE statement for a PL/SQL procedure.

  public static int execDelete(String filmName) throws SQLException {

    // Declare return code and DELETE statement variables.

      int returnCode = 0;

    String deleteStatement = "DELETE FROM film WHERE film_name = ?";

 

    // Declare a connection.

    Connection conn =

      DriverManager.getConnection("jdbc:default:connection:");

     

      // Declare a connection and execute a DELETE statement.

      try {

      PreparedStatement ps = conn.prepareStatement(deleteStatement);

      ps.setString(1,filmName);

      ps.execute();

      conn.close(); }

    catch (SQLException e) {

      returnCode = e.getErrorCode(); } // Set error or return code.

    finally {

      if (conn != null) {

        conn.close(); }}

    // Return zero or error code.

      return returnCode;

  }

 

  // Execute a Query for a PL/SQL function returning a single row.     

  public static String query(String filmName) throws SQLException {

    // Declare a query statement.

    String query = "SELECT film_name FROM film WHERE film_name = ?";

 

    // Define and initialize a local return variable.

    String returnString = new String();

 

    // Declare a connection.

    Connection conn =

      DriverManager.getConnection("jdbc:default:connection:");

     

      // Declare a connection and execute a DELETE statement.

      try {

      PreparedStatement ps = conn.prepareStatement(query);

      ps.setString(1,filmName);

      ResultSet rs = ps.executeQuery();

      while (rs.next()) {

        returnString = rs.getString(1);

          break; }}

    catch (SQLException e) {}

      finally {

        return returnString; }

    }

}

You can compile the sql.java file externally and use the loadjava utility to load it into the Oracle database. This example loads it into the student schema with the following syntax:

loadjava -r -f -o -user student/student sql.class

This section has shown you how to write and deploy a Java library that supports insert, update, and delete operations.

Wrapping Java Libraries with PL/SQL

This section provides you with the PL/SQL wrappers for the Java libraries. Each of the wrappers gives a SQL function or procedure name and maps those names to one of the Java methods in the sql.class file. This section also provides the SQL to create the film table.

You create the film table with this syntax in the Oracle Database 12c:

CREATE TABLE film

( film_id    NUMBER GENERATED AS IDENTITY

, film_name  VARCHAR2(40));

You could put these in a package but to keep things very simple, they’re standalone functions and procedures. You should take note that the semicolon terminates the PL/SQL wrapper and the forward slash (/) dispatches the CREATE statement.

The first three PL/SQL wrappers support procedures because those Java libraries don’t raise exceptions or return something that lets your program know whether the calls were successful or not. Here are the three procedures:

CREATE OR REPLACE PROCEDURE run_insert

(film_name  VARCHAR2) IS LANGUAGE JAVA

NAME 'sql.runInsert(java.lang.String)';

/

 

CREATE OR REPLACE PROCEDURE run_update

( old_film_name  VARCHAR2

, new_film_name  VARCHAR2) IS LANGUAGE JAVA

NAME 'sql.runUpdate(java.lang.String, java.lang.String)';

/

 

CREATE OR REPLACE PROCEDURE run_delete

(film_name  VARCHAR2) IS LANGUAGE JAVA

NAME 'sql.runDelete(java.lang.String)';

/

The next three PL/SQL wrappers support functions because the related Java libraries return zero when they work and an exception number when they fail. Here are the three functions:

CREATE OR REPLACE FUNCTION exec_insert

(film_name  VARCHAR2) RETURN NUMBER IS LANGUAGE JAVA

NAME 'sql.execInsert(java.lang.String) return int';

/

 

CREATE OR REPLACE FUNCTION exec_update

( old_film_name  VARCHAR2

, new_film_name  VARCHAR2) RETURN NUMBER IS LANGUAGE JAVA

NAME 'sql.execUpdate(java.lang.String, java.lang.String) return int';

/

 

CREATE OR REPLACE FUNCTION exec_delete

(film_name  VARCHAR2) RETURN NUMBER IS LANGUAGE JAVA

NAME 'sql.execDelete(java.lang.String) return int';

/

The last PL/SQL wrapper is a function that returns the film name, which lets us confirm whether the prior operations succeed or not.

CREATE OR REPLACE FUNCTION query

(film_name  VARCHAR2) RETURN VARCHAR2 IS LANGUAGE JAVA

NAME 'sql.query(java.lang.String) return java.lang.String';

/

This section has shown you how to write PL/SQL procedures that wrap Java libraries that return a void and PL/SQL functions that wrap Java libraries that return a number or string.

Creating and Using Test Cases

After you’ve write and deploy your Java libraries and PL/SQL wrappers, you need to test whether the code works. This section provides two test cases in anonymous PL/SQL blocks.

One tests the Java libraries that return a void and their respective PL/SQL procedures. The other tests the Java libraries that return an int and their respective PL/SQL functions.

This block tests the procedure approach:

SQL> DECLARE

  2    /* Local variables. */

  3    film1  VARCHAR2(40) := 'Cars';

  4    film2  VARCHAR2(40) := 'Cars 2';

  5  BEGIN

  6    /* Insert test case. */

  7    run_insert('Cars');

  8    dbms_output.put_line('Insert [' || query(film1) || ']');

  9    /* Update test case. */

 10    run_update(film1,film2);

 11    dbms_output.put_line('Update [' || query(film2) || ']');

 12    /* Delete test case. */

 13    run_delete(film2);

 14    dbms_output.put_line('Delete [' || query(film2) || ']');

 15  END;

 16  /

It prints

Insert [Cars]

Update [Cars 2]

Delete []

This block tests the function approach:

SQL> DECLARE

  2    /* Local variables. */

  3    film1  VARCHAR2(40) := 'Cars';

  4    film2  VARCHAR2(40) := 'Cars 2';

  5  BEGIN

  6    /* Insert test case. */

  7    IF exec_insert('Cars') = 0 THEN

  8      dbms_output.put_line('Insert [' || query(film1) || ']');

  9    END IF;

 10

 11    /* Update test case. */

 12    IF exec_update(film1,film2) = 0 THEN

 13      dbms_output.put_line('Update [' || query(film2) || ']');

 14    END IF;

 15

 16    /* Delete test case. */

 17    IF exec_delete(film2) = 0 THEN

 18      dbms_output.put_line('Delete [' || query(film2) || ']');

 19    END IF;

 20  END;

 21  /

It prints

Insert [Cars]

Update [Cars 2]

Delete []

This article has shown you how to write, deploy, and test internal Java libraries.