6.8 Using Enterprise JavaBeans
Enterprise JavaBeans (EJB) is an architecture that specifies how to write distributed Java components, referred to 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 these, a bean developer can concentrate on the business logic provided by the bean. The Oracle JServer has the capability to run EJB, and Oracle8i Version 8.1.6 of the JServer implements Version 1.0 of the EJB standard.
This section is not intended to be a rigorous introduction to the complex subject of EJB, but will give you enough information to create an EJB containing SQLJ for the JServer. For a detailed discussion of EJB programming, I recommend the book Enterprise JavaBeans by Richard Monson-Haefel (O'Reilly). In this section, I introduce a simple bean that uses SQLJ to retrieve a row from the customers table, and show how to deploy this bean to the JServer. Finally, a client program will invoke the bean to retrieve a customer record from the customers table and display the retrieved column values for you to see.
6.8.1 Bean Types
Beans come in two types: entity beans and session beans. Entity beans are used to model physical objects, such as a product. Session beans are used to model specific tasks, such as placing an order for a product. Oracle8i Version 8.1.6 only allows the use of session beans, although Version 8.1.7 supports entity beans.
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."
6.8.2 Writing a Session Bean
This section shows you how to write a session bean that uses SQLJ to retrieve a row from the customers table. The bean will be named Customer. Before you can create a bean, you need to understand the four parts that make up a bean:
The next few sections discuss these four parts. You'll learn the purpose of each part, and see how to implement each part for the Customer bean created in this section.
6.8.2.1 The remote interface
The remote interface defines the methods that implement the business logic of the component. The methods defined in the remote interface are those 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 Example 6-3.
Example 6-3. Customer.java
/* 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 // ftom 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 Example 6-4.
Example 6-4. CustomerRecord.java
/* 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.
6.8.2.2 The home interface
The home interface defines the methods used to manage the life cycle of the bean, and contains methods used to create, find, and destroy a bean instance. Not every home interface implements every type of method. The home interface for the Customer bean, defined in the file CustomerHome.java, is shown in Example 6-5.
Example 6-5. CustomerHome.java
/* 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 an instance of the Customer bean. When the create( ) method is called, the ejbCreate( ) method defined in CustomerBean.sqlj is transparently called for you. The EJB server manages the eventual destruction of the bean automatically.
6.8.2.3 The bean class
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 Example 6-6.
Example 6-6. CustomerBean.sqlj
/* 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:
- ejbCreate( )
-
Runs when the new bean instance is created, and may be used to initialize the bean instance.
- ejbActivate( )
-
Runs when the bean instance is about to be activated.
- ejbPassivate( )
-
Runs when the bean instance is about to be deactivated.
- ejbRemove( )
-
Runs when the bean instance is about to be destroyed, and may be used to clean up after the bean instance.
- setSessionContext( )
-
Runs after the bean instance is created, and is used to enable the bean to retrieve the session context.
These methods are not 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, which 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 JServer, which has an implicit connection to the database.
6.8.2.4 The deployment descriptor
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 Example 6-7.
Example 6-7. customer.ejb
/* customer.ejb is the deployment descriptor for the Customer bean. */ SessionBean customer.CustomerBean { BeanHomeName = "test/customerBean"; RemoteInterfaceClassName = customer.Customer; HomeInterfaceClassName = customer.CustomerHome; AllowedIdentities = {FUNDAMENTAL_USER}; StateManagementType = STATEFUL_SESSION; RunAsMode = CLIENT_IDENTITY; TransactionAttribute = TX_REQUIRED; }
The attributes in the deployment descriptor have the following uses:
- BeanHomeName
-
Specifies the bean's directory and published name. A bean directory refers to a storage area within the database that is used to store beans. The test directory is created when Oracle8i is installed.
- RemoteInterfaceClassName
-
Specifies the fully qualified name for the remote interface class.
- HomeInterfaceClassName
-
Specifies the fully qualified name for the home class.
- AllowedIdentities
-
Specifies the name of one or more users who are allowed to access the bean. To specify multiple users, use a comma-delimited list of usernames.
- StateManagementType
-
Specifies whether the bean is stateful (STATEFUL_SESSION) or stateless (STATELESS_SESSION).
- RunAsMode
-
Specifies the identity used to run the bean. A value of CLIENT_IDENTITY indicates that the bean is run using the identity of the client program that calls it.
- TransactionAttribute
-
Specifies the level of database transaction management provided by the EJB server for the bean. A value of TX_REQUIRED indicates that transaction management is required for the bean.
6.8.3 Compiling and Deploying the Bean
I have created an MS-DOS batch file named bean.bat, available on the book's web site, which contains the necessary commands to compile and deploy the Customer bean to the JServer. For those of you using Linux or another flavor of Unix, you can either type in the necessary Linux/Unix commands or write a similar script. The contents of this file are shown in Example 6-8.
Example 6-8. bean.bat
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 fundamental_user -password fundamental_password -service %ORACLE_SERVICE% -descriptor customer.ejb customer.jar
To compile and deploy the bean, run bean.bat using the MS-DOS command line:
D:\> bean.bat
The bean.bat file begins by initializing the following three environment variables:
- ORACLE_HOME
-
Points to the directory in which the Oracle8i software was installed.
- ORACLE_SERVICE
-
Points to the Internet Inter-Orb Protocol (IIOP) service for the Oracle8i database. IIOP is a lower-level protocol over which RMI runs.
- CLASSPATH
-
Specifies a list of all the class libraries required to compile the Customer bean.
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 utility to compile the following files:
-
CustomerRecord.java
-
Customer.java
-
CustomerHome.java
The sqlj command-line utility is then used to translate and compile the CustomerBean.sqlj file. Next, bean.bat uses the jar command-line utility to build a JAR file named customer.jar that containsthe various class files. Finally, bean.bat loads the Customer bean into the database using the Oracle deployejb command-line utility:
call deployejb -republish -temp temp -user fundamental_user -password fundamental_password -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 then used to store the temporary files generated by the deployejb utility.
| See Appendix B for a description of all the command-line options for the deployejb command-line utility. |
|
6.8.4 The Client Program
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 Example 6-9,invokes the Customer bean.
Example 6-9. Client.java
/* The program Client.java illustrates how to use the Customer bean. */ // import the Customer bean classes import customer.Customer; import customer.CustomerHome; import customer.CustomerRecord; // import the Java Naming and Directory Interface classes import oracle.aurora.jndi.sess_iiop.ServiceCtx; import javax.naming.Context; import javax.naming.InitialContext; import java.util.Hashtable; // import java.sql.Date (required for the dob attribute) import java.sql.Date; public class Client { public static void main(String [] args) throws Exception { // get the arguments from the command line if (args.length != 4) { System.out.println("usage: Client service_URL bean_name " + "username password"); System.exit(1); } String service_URL = args[0]; String bean_name = args[1]; String username = args[2]; String password = args[3]; // initialize the Java Naming and Directory Interface (JNDI) // environment so that the Client program can run the Customer bean 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); // create a CustomerHome object and use the lookup( ) method to // locate the Customer bean CustomerHome customer_home = (CustomerHome) ic.lookup(service_URL + bean_name); // create a Customer object and call the create( ) method to create // an instance of the Customer bean Customer customer_bean = customer_home.create( ); // create a CustomerRecord object and set it to the row returned // from the getRow( ) method contained in the Customer bean CustomerRecord customer_record = customer_bean.getRow(1); // display the CustomerRecord attributes 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 main( ) method in this program gets the following arguments from the command line:
-
The IIOP service
-
The bean name
-
The database username
-
The database password
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 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);
Example 6-10 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.
Example 6-10. client.bat
REM MS-DOS script for compiling and running the client program that uses the REM 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 fundamental_user fundamental_password
Again, in this batch file, the environment variables ORACLE_HOME, ORACLE_SERVICE, and CLASSPATH are set. If you execute this file, ensure these variables are set correctly for your environment. Next, the javac command is issued to compile the Client.java file, and 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), fundamental_user (the username), and fundamental_password (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