I recently had a situation where I needed to execute the same exact business logic in PL/SQL as in a Java based service. The business logic had already been implemented in Java. The logic took a person’s full name and parsed it out into it’s components, first name, middle name, last name, salutation (Mr., Mrs., Ms., Dr., etc.) and suffix, (Sr., Jr., III, IV, Esq., etc.).
I knew that I could implement a PL/SQL function that was written in Java and import it into the database. However an example was hard to find. I found examples where the function returned a primitive or a String, but nothing that returned a more complex data structure.
I’ve done similar things with Java calling a Stored Procedure that accepted a PL/SQL structure or table. To do this you define a PL/SQL type and then run JPublisher to create classes that represented the type in Java. The type could then be used to bind to JDBC SQL statements that passed Objects. I thought that perhaps the reverse would work as well.
The basic steps are as follows:
- Identify the existing Java code that will be needed.
- Create the PL/SQL Interface.
- Create Java code that implements the PL/SQL Interface.
- Define the PL/SQL.
- Load Java code into the Database.
Existing Java Code
So let’s start with the Java code that implemented the business logic. I’m not going to reproduce the name parsing code here. I’ll simply define the interface.
package com.company.nameparse;
public class NameParser {
public static NameComponentsDTO parse(String rawName) { … }
}
The DTO Object
package com.company.nameparse;
public class NameComponentsDTO implements Serializable {
private String salutation;
private String firstName;
private String middleInitialOrName;
private String lastName;
private String suffix;
private String originalNameString;
// Getters and Setters for each of the private members.
// …
}
You need to be careful as to what version of the JRE is running in your version of Oracle. The target Oracle version I was working with is 10g (10.0.2). This version used the 1.4.2 JRE. So the Name Parsing class had to be stripped down of things like Annotations, Prototypes on collections and even some newer methods on java.lang.String. I believe that 11g uses the JRE 1.5. So any 1.6 features will not be available.
Well maybe you can upgrade the JRE? Well I asked that question of Tom Kyte on AskTom.Oracle.com. His reply was that you couldn’t change the JRE. That Oracle uses it internally and it needs to have known behavior.
PL/SQL Interface
Now lets define what we want the PL/SQL Function to look like. I like to define things in Packages. So here is the package definition:
CREATE OR REPLACE PACKAGE NAMEPARSEPACKAGE AS
function nameparse( fullname VARCHAR2 ) return NAME_COMPONENT_STRUCT;
END NAMEPARSEPACKAGE;
/
Notice the function returns a PL/SQL Structure. Here the definition of the Structure.
CREATE OR REPLACE TYPE NAME_COMPONENT_STRUCT AS object (
firstName varchar2(40),
lastname varchar2(40),
middle varchar2(40),
salutation varchar2(40),
suffix varchar2(40),
originalName varchar2(80)
);
/
Java Implementation
Now for creating the Java code that implements this PL/SQL interface. We need to create a Java class that implements the Structure that is returned by the function nameparse. The easiest way to do this is to use the JPublish command line utility to create a Java class from the Struct. The JPublish command line is included with the Oracle Companion CD.
First you need to make sure you define your JAVA_HOME. This JAVA_HOME must point to a JDK, not a JRE.
Then you need to make sure you have your CLASSPATH set properly for the JPublish command line.
set CLASSPATH=%ORACLE_HOME%\sqlj\lib\translator.jar;%ORACLE_HOME%\sqlj\lib\runtime12.jar;%ORACLE_HOME%\jdbc\lib\ojdbc14.jar;%ORACLE_HOME%\sqlj\lib\dbwsa.jar;
Then you can run JPublish. You need to pass the user/id and TNSName as well as the SQL type you want to create. You can optionally provide a Java package.
jpub -user=scott/tiger@DEV -numbertypes=oracle -builtintypes=oracle -sql=NAME_COMPONENT_STRUCT -package=com.company.nameparser.oracle
Your going to get 2 classes NAME_COMPONENT_STRUCT and NAME_COMPONENT_STRUCTRef. In these classes, there are constants that define the schema name and Oracle type name.
public static final String _SQL_NAME = "SCOTT.NAME_COMPONENT_STRUCT";
public static final String _SQL_BASETYPE = "SCOTT.NAME_COMPONENT_STRUCT";
You might want to hand modify these constants remove the schema name so this Java this code can call on schemas other than the schema you ran Jpublisher with.
The code to wrap the original Java code and return the Struct is very simple. The method that implements the function must be public and static.
package com.company.nameparser.oracle;
import java.sql.SQLException;
import oracle.sql.CHAR;
import com. company.nameparse.NameComponentsDTO;
import com. company.nameparse.NameParser;
public class NameParserOracleWrapper {
public static NAME_COMPONENT_STRUCT parse(String rawName) {
NAME_COMPONENT_STRUCT struct = new NAME_COMPONENT_STRUCT();
NameComponentsDTO dto = NameParser.parse(rawName);
try {
struct.setFirstname(new CHAR(dto.getFirstName(),
CHAR.DEFAULT_CHARSET));
struct.setLastname(new CHAR(dto.getLastName(), CHAR.DEFAULT_CHARSET));
struct.setMiddle(new CHAR(dto.getMiddleInitialOrName(),
CHAR.DEFAULT_CHARSET));
struct.setSalutation(new CHAR(dto.getSalutation(),
CHAR.DEFAULT_CHARSET));
struct.setSuffix(new CHAR(dto.getSuffix(), CHAR.DEFAULT_CHARSET));
struct.setOriginalname(new CHAR(dto.getOriginalNameString(),
CHAR.DEFAULT_CHARSET));
} catch (SQLException e) {
}
return struct;
}
}
PL/SQL Implementation
Now to implement the PL/SQL Function.
CREATE OR REPLACE PACKAGE BODY NAMEPARSEPACKAGE IS
function nameparse(fullname VARCHAR2)
return NAME_COMPONENT_STRUCT as
language java
name 'com.company.nameparser.oracle.NameParserOracleWrapper.parse(java.lang.String) return com.company.nameparser.oracle.NAME_COMPONENT_STRUCT';
END NAMEPARSEPACKAGE;
/
Load the Java Code
The last thing to be done is to load the Java code into Oracle. You need to make sure you have all of the classes necessary loaded. There is a Maven plugin comes in handy in situations like this. It is called the Shade plugin. It pulls in all the classes from all of the dependencies for a Maven project. Below is the pom.xml file that implements this. Notice that Oracle is a provided dependency. The Shade plugin will not include classes from this project.
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.company.nameparse</groupId>
<artifactId>nameparse-oracle</artifactId>
<version>1.0.0-SNAPSHOT</version>
<packaging>jar</packaging>
<name>nameparse-oracle</name>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<java.version>1.4</java.version>
</properties>
<build>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>${java.version}</source>
<target>${java.version}</target>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>ojdbc</groupId>
<artifactId>ojdbc</artifactId>
<version>14</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.company.nameparse</groupId>
<artifactId>nameparse</artifactId>
<version>1.0.0-SNAPSHOT</version>
</dependency>
</dependencies>
</project>
When you build this project you will get a Jar file nameparse-oracle-1.0.0-SNAPSHOT.jar
Now that you have the jar file you need to load it into Oracle. Included in the Oracle client is the loadjava. You can load the entire jar file with this tool.
loadjava -user=scott/tiger@DEV nameparse-oracle-1.0.0-SNAPSHOT.jar
You should not be able to run your PL/SQL function.
declare
dto NAME_COMPONENT_STRUCT;
begin
DBMS_OUTPUT.ENABLE;
dto := NAMEPARSEPACKAGE.NAMEPARSE('Mr. Joseph E. Kampf Jr.');
DBMS_OUTPUT.PUT_LINE(dto.originalName);
DBMS_OUTPUT.PUT_LINE(dto.saluation);
DBMS_OUTPUT.PUT_LINE(dto.firstname);
DBMS_OUTPUT.PUT_LINE(dto.middle);
DBMS_OUTPUT.PUT_LINE(dto.lastname);
DBMS_OUTPUT.PUT_LINE(dto.suffix);
end;
You should see the following DBMS_OUTPUT:
Mr. Joseph E. Kampf Jr.
Mr.
Joseph
E.
Kampf
Jr.