Both the Oracle9i and Oracle8i Relational Database Management Systems (RDBMS) contain an integrated Java Virtual Machine, known as the Oracle JVM. This allows Java programs to be run from inside the database, and it means that stored procedures, functions, and triggers that previously had to be written using Oracle's proprietary PL/SQL language can now be written in SQLJ. In addition, the Oracle Internet Application Server, or iAS, also contains an Oracle JVM, which means you can deploy SQLJ programs in the middle tier, as well as in the database.
The Oracle JVM is compatible with the standard Java language specification and Java Virtual Machine specification. The Oracle JVM contains a CORBA-compliant Object Request Broker (ORB), and the Oracle JVM supports the Enterprise JavaBeans standard.
The Oracle JVM runs in the same process and memory space as the database kernel. This results in a significant increase in the scalability and performance of your Java stored programs, because the memory heaps and data structures of the RDBMS are available directly to those Java programs. In addition, Oracle provides the standard Java class libraries, such as java.lang, java.util, and java.math, precompiled for the native platform on which the Oracle JVM runs, resulting in an additional boost in performance.
Enterprise JavaBeans is an architecture that specifies how to write distributed Java components, which are referred to in this article as beans. The beans are stored in an EJB server, which provides a number of services that may be used by the beans. Such services include:
Because the EJB server handles the complexities of providing services such as those listed here, a bean developer can concentrate on the business logic to be provided by the bean.
This section is not intended to be a rigorous introduction to the complex subject of EJBs, but it is intended to give you enough information to create an EJB containing SQLJ for the Oracle JVM. For a detailed discussion of Enterprise JavaBeans programming, I recommend the book Enterprise JavaBeans, 3rd Edition by Richard Monson-Haefel. In this section, we will examine a simple bean that uses SQLJ to retrieve a row from a database table named customers. The customers table is created using the following SQL statement:
CREATE TABLE customers ( id NUMBER CONSTRAINT customers_pk PRIMARY KEY, first_name VARCHAR2(50) NOT NULL, last_name VARCHAR2(50) NOT NULL, dob DATE, phone VARCHAR2(20) );
In this article, I've assumed you are using the "scott" database user, which is created in the Oracle test database, with a password of "tiger," although you can use any database user. The customers table is populated using the following SQL statements:
INSERT INTO customers (id, first_name, last_name, dob, phone) VALUES (1, 'John', 'Smith', '01-JAN-1965', '650-555-1212'); INSERT INTO customers (id, first_name, last_name, dob, phone) VALUES (2, 'Cynthia', 'Stevens', '05-MAR-1968', '650-555-1213'); INSERT INTO customers (id, first_name, last_name, dob, phone) VALUES (3, 'Steve', 'Seymour', '16-JUN-1971', '650-555-1214'); INSERT INTO customers (id, first_name, last_name, dob, phone) VALUES (4, 'Gail', 'Williams', '01-DEC-1975', '650-555-1215'); INSERT INTO customers (id, first_name, last_name, dob, phone) VALUES (5, 'Doreen', 'Heyson', '20-AUG-1970', '650-555-1216');
So, the customers table contains the following rows:
ID FIRST_NAME LAST_NAME DOB PHONE ---- ---------- ---------- --------- ------------ 1 John Smith 01-JAN-65 650-555-1212 2 Cynthia Stevens 05-MAR-68 650-555-1213 3 Steve Seymour 16-JUN-71 650-555-1214 4 Gail Williams 01-DEC-75 650-555-1215 5 Doreen Heyson 20-AUG-70 650-555-1216
You will next see how to deploy a bean to the Oracle JVM, and finally, I will show you a client program that will invoke the bean to retrieve the column values for customer #1 from the customers table, and then display the retrieved column values.
|
Related Reading
|
Beans come in two types: entity beans and session beans. Entity beans are used to model physical objects: a product, for example. Session beans are used to model specific tasks: placing an order for a product, for example. This article shows you how to create a session bean.
For a list of all of O'Reilly's Oracle books, visit oracle.oreilly.com, and for a list of Java books, check out java.oreilly.com.
Session beans themselves come in two types: stateless and stateful. A stateless session bean does not keep the same state between calls; a stateful session bean does. An easy way to understand the difference between stateful and stateless session beans is as follows: a stateful session bean may be used to maintain a "conversation" with a client program because the bean "remembers" what was last said to the client program, whereas a stateless session bean "forgets."
This section will show you how to write a session bean that uses SQLJ to retrieve a row from the customers table. The bean will be named Customer and was tested using Oracle8i version 8.1.6. Before you can create a bean, you need to understand the four parts that make up a bean:
The next few sections talk about these four parts. You'll learn the purpose of each part, and you'll see how to implement each part for the Customer bean that we are creating.
The remote interface defines the methods that implement the business logic of the component. The methods that are defined in the remote interface are those that are intended to be available to the outside world. Any methods that are private to the internal operation of the bean should not be included in the remote interface. You can think of the remote interface as the specification for a bean. The remote interface for the Customer bean is contained in the file Customer.java and is shown in the following example:
/*
Customer.java is the remote interface
for the Customer bean.
*/
package customer;
import javax.ejb.EJBObject;
import java.rmi.RemoteException;
import java.sql.SQLException;
public interface Customer extends EJBObject {
// getRow() retrieves the specified row
// from the customers table
public CustomerRecord getRow(int id)
throws SQLException, RemoteException;
}
All remote interfaces extend the javax.ejb.EJBObject interface. There is one public method for the Customer bean: getRow(). The getRow() method retrieves a row from the customers table for which the id column value equals the id parameter value. As you will see later, the getRow() method uses SQLJ to retrieve the row. All the Java files are organized into a Java package named customer.
The getRow() method throws the exceptions java.rmi.RemoteException and java.sql.SQLException. The java.rmi.RemoteException is used to notify the client program when a Remote Method Invocation (RMI) error occurs in the bean. The RMI protocol is used when the client program and the bean communicate. The java.sql.SQLException is used to notify the client program when a SQL error occurs in the bean.
The getRow() method returns a CustomerRecord object to the client program invoking the method. The CustomerRecord class, defined in the file CustomerRecord.java, is shown in the following example:
/*
CustomerRecord.java defines the CustomerRecord class
*/
package customer;
import java.sql.Date;
public class CustomerRecord implements java.io.Serializable {
// define the attributes
public int id;
public String first_name;
public String last_name;
public Date dob;
public String phone;
// define the constructor
public CustomerRecord(
int id,
String first_name,
String last_name,
Date dob,
String phone
) {
this.id = id;
this.first_name = first_name;
this.last_name = last_name;
this.dob = dob;
this.phone = phone;
}
}
The CustomerRecord class has five attributes: id, first_name, last_name, dob, and phone. These attributes are used to store the values of the respective columns from the customers table. The constructor is the only method in the class, and it is used to set the attributes id, first_name, last_name, dob, and phone using the parameter values passed to it. Since the attributes are public, no additional methods are required to access them. The use of public attributes is not necessarily recommended for your own programs; I only use them here to keep the EJB small.
The home interface defines the methods that are used to manage the life cycle of the bean, and it contains methods that are used to create, find, and destroy a bean instance. Not every home interface will implement every type of method. The home interface for the Customer bean, defined in the file CustomerHome.java, is shown in the following example:
/*
CustomerHome.java is the home interface
for the Customer bean.
*/
package customer;
import javax.ejb.*;
import java.rmi.RemoteException;
public interface CustomerHome extends EJBHome {
// the create() method is used to create
// an instance of the Customer bean.
public Customer create()
throws CreateException, RemoteException;
}
The home interface extends the javax.ejb.EJBHome class. Only the create() method has been defined, and it is used to instantiate a Customer bean. When the create() method is called, the ejbCreate() method defined in CustomerBean.sqlj will be transparently called for you. The EJB server will manage the eventual destruction of the bean automatically.
The bean class contains the actual code for the public methods defined in the remote interface, plus the code for any private methods that are part of the class. The bean class for the Customer bean, defined in the file CustomerBean.sqlj, is shown in the following example:
/*
CustomerBean.sqlj is the bean class for the customer bean.
This file contains the implementation of the methods
defined in the remote interface.
*/
package customer;
import customer.CustomerRecord;
import java.sql.*;
import java.rmi.RemoteException;
import javax.ejb.*;
// CustomerBean is a session bean
public class CustomerBean implements SessionBean {
public void ejbCreate() throws CreateException, RemoteException {}
public void ejbActivate() {}
public void ejbPassivate() {}
public void ejbRemove() {}
public void setSessionContext(SessionContext ctx) {}
// getRow() retrieves the specified row from the customers table
// and returns the row in the form of a CustomerRecord object
public CustomerRecord getRow(
int id
) throws SQLException, RemoteException
{
String first_name;
String last_name;
Date dob;
String phone;
// retrieve the row using SQLJ
#sql {
SELECT
first_name, last_name, dob, phone
INTO
:first_name, :last_name, :dob, :phone
FROM
customers
WHERE
id = :id
};
// create and return a CustomerRecord object that contains
// the column values for the row
return new CustomerRecord(id, first_name, last_name, dob, phone);
}
}
The CustomerBean class implements the SessionBean interface, meaning that CustomerBean is a session bean. The CustomerBean class contains the following five method stubs:
None of these methods are actually implemented by the CustomerBean class, but they are required by the EJB component model.
The getRow() method, which is implemented by the class, retrieves a row from the customers table for a specified customer. You specify the customer by passing the customer's ID number as the value for the method's id parameter. The getRow() method retrieves the row using a SQLJ statement, and returns a new instance of the CustomerRecord class. The retrieved column values are passed to the CustomerRecord constructor, and that constructor sets the CustomerRecord attributes to those values. The CustomerRecord object is then returned to the client that called the getRow() method.
You will notice that the Customer bean doesn't make an explicit connection to the database. This is because the bean will be deployed in the Oracle JVM, which has an implicit connection to the database.
The deployment descriptor is used to set the runtime attributes for a bean. A deployment descriptor is stored in a file and, as will be shown later, is used when a bean is deployed. The deployment descriptor for CustomerBean is defined in the file customer.ejb, and is shown in the following example:
/*
customer.ejb is the deployment descriptor
for the Customer bean.
*/
SessionBean customer.CustomerBean {
BeanHomeName = "test/customerBean";
RemoteInterfaceClassName = customer.Customer;
HomeInterfaceClassName = customer.CustomerHome;
AllowedIdentities = {SCOTT};
StateManagementType = STATEFUL_SESSION;
RunAsMode = CLIENT_IDENTITY;
TransactionAttribute = TX_REQUIRED;
}
The attributes in the deployment descriptor have the following uses:
The following example shows an MS-DOS batch file named bean.bat, which contains the necessary commands to compile and deploy the Customer bean to the Oracle JVM. For those of you using Linux or another flavor of Unix, you can either type in the necessary Linux/Unix commands or you can write a similar script.
REM MS-DOS script for compiling and deploying the Customer bean. REM set the required environment variables set ORACLE_HOME=E:\Oracle\Ora81 set ORACLE_SERVICE=sess_iiop://localhost:2481:ORCL set CLASSPATH=.;%ORACLE_HOME%\lib\aurora_client.jar; %ORACLE_HOME%\jdbc\lib\ classes111.zip;%ORACLE_HOME%\sqlj\lib\translator.zip; %ORACLE_HOME%\lib\ vbjorb.jar;%ORACLE_HOME%\lib\vbjapp.jar REM compile the Customer bean files javac customer\CustomerRecord.java javac customer\Customer.java javac customer\CustomerHome.java sqlj -ser2class customer\CustomerBean.sqlj REM build the jar file containing the classes jar -cf0 customer.jar customer/Customer.class customer/CustomerRecord.class customer/CustomerHome.class customer/CustomerBean.class customer/CustomerBean_SJProfile0.class customer/CustomerBean_SJProfileKeys.class REM deploy the Customer bean using the deployejb utility call deployejb -republish -temp temp -user scott -password tiger -service %ORACLE_SERVICE% -descriptor customer.ejb customer.jar
To compile and deploy the bean, run the bean.bat file using the MS-DOS command line:
D:\> bean.bat
The bean.bat file begins by initializing the following three environment variables:
Before running the bean.bat file, be sure that the values used to initialize these variables are correct for your environment.
The bean.bat file uses the javac command-line tool to compile the following files:
The sqlj command-line tool is then used to translate and compile the CustomerBean.sqlj file. Next, bean.bat uses the jar command-line tool to build a Java Archive file named customer.jar that contains the various class files.
For more tips on using SQLJ, don't miss this other oreilly.com article by Jason Price: Using Static and Dynamic SQL in SQLJ.
Finally, bean.bat loads the Customer bean into the database using the Oracle deployejb command-line tool (I've assumed you are deploying the bean using a database user named "scott" with a password of "tiger."):
call deployejb -republish -temp temp -user scott -password tiger -service %ORACLE_SERVICE% -descriptor customer.ejb customer.jar
This deployejb command loads the customer.jar file into the database using the information contained in the customer.ejb descriptor file. The -temp option specifies a directory that is created, and that is then used to store the temporary files generated by the deployejb tool.
Once you've loaded the Customer bean into the database, you can develop a client program that invokes the bean. The program Client.java, shown in this example, invokes the Customer bean.
The main() method in this program gets the following arguments from the command line:
Next, after the command-line arguments are retrieved, the Java Naming and Directory Interface (JNDI) is initialized:
Hashtable env = new Hashtable(); env.put(Context.URL_PKG_PREFIXES, "oracle.aurora.jndi"); env.put(Context.SECURITY_PRINCIPAL, username); env.put(Context.SECURITY_CREDENTIALS, password); env.put(Context.SECURITY_AUTHENTICATION, ServiceCtx.NON_SSL_LOGIN); Context ic = new InitialContext(env);
JNDI is used to access the Customer bean that is stored in the database. The env hashtable is used to store the parameters that authenticate the user attempting to run the Customer bean. Once JNDI has been initialized, a CustomerHome object is created:
CustomerHome customer_home = (CustomerHome) ic.lookup(service_URL + bean_name);
The call to the lookup() method in the Context class uses the IIOP service name together with the bean name to retrieve the Customer bean. The CustomerHome object is used to call the methods in the bean home interface.
After the bean has been retrieved, a Customer object is created:
Customer customer_bean = customer_home.create();
This call to the create() method in the CustomerHome class creates an instance of the Customer bean. A CustomerRecord object is then created to hold the row returned from the Customer bean's getRow() method:
CustomerRecord customer_record = customer_bean.getRow(1);
At this point, a call to the getRow() method retrieves the column values for the row in the customers table with an id of 1.
CustomerRecord customer_record = customer_bean.getRow(1);
Finally, the values contained in the customer_record attributes are displayed:
System.out.println("Customer Information:");
System.out.println("id = " + customer_record.id);
System.out.println("first_name = " + customer_record.first_name);
System.out.println("last_name = " + customer_record.last_name);
System.out.println("dob = " + customer_record.dob);
System.out.println("phone = " + customer_record.phone);
The following example shows an MS-DOS batch file named client.bat. The commands in this batch file compile and run the client program that I've just discussed.
REM MS-DOS script for compiling and running the client program REM that uses the Customer EJB. REM set the required environment variables set ORACLE_HOME=E:\Oracle\Ora81 set ORACLE_SERVICE=sess_iiop://localhost:2481:ORCL set CLASSPATH=.;%ORACLE_HOME%\lib\aurora_client.jar; %ORACLE_HOME%\jdbc\lib\ classes111.zip;%ORACLE_HOME%\sqlj\lib\translator.zip; %ORACLE_HOME%\lib\ vbjorb.jar;%ORACLE_HOME%\lib\vbjapp.jar;customer_generated.jar REM compile the client program javac Client.java REM run the client program java Client %ORACLE_SERVICE% /test/customerBean scott tiger
Once again, in this batch file, the environment variables ORACLE_HOME, ORACLE_SERVICE, and CLASSPATH are set. If you execute this file, be sure that these variables are set correctly for your environment. Next, the javac command is issued to compile the Client.java file. Finally, the java command is used to run the client program. Notice the four arguments that are passed. As mentioned earlier, these are: %ORACLE_SERVICE% (the IIOP service name), /test/customerBean (the name of the bean), scott (the username), and tiger (the password). The main() method in the client program retrieves the arguments, creates an instance of the Customer bean, and displays the column values retrieved from the customers table.
The following example shows the client.bat file being invoked from the MS-DOS prompt in order to compile and run the Client program:
D:\> client.bat Customer Information: id = 1 first_name = John last_name = Smith dob = 1965-01-01 phone = 650-555-1212
I hope you've enjoyed reading this article and are interested in learning more about SQLJ. There are several sources for more information on SQLJ, including Oracle's Technet Web site and the SQLJ Web site. You can also read my book, Java Programming with Oracle SQLJ .
Jason Price is currently employed as a product manager with Oracle. He has contributed to many products at Oracle, including the database server, the application server, and several CRM applications. Prior to working at Oracle, he was employed as a consultant at Logica PLC in London, a systems integration and technical consulting firm. He is a certified Oracle DBA and Application Developer, and he has over ten years of experience in the software industry. When Jason is not working or writing, he enjoys walking, running, and working out. He holds a Bachelor of Science degree (with honors) in Physics from the University of Bristol, England. Jason currently resides in California.
O'Reilly & Associates recently released (August 2001) Java Programming with Oracle SQLJ.
Sample Chapter 8, Contexts and Multithreading, is available free online.
You can also look at the Table of Contents, the Index, and the Full Description of the book.
For more information, or to order the book, click here.
Copyright © 2009 O'Reilly Media, Inc.