It is recommended that you avoid accessing EJB entity beans from client or servlet code. Instead, wrap and access EJB entity beans from EJB session beans. This improves performance in two ways:
Except for the basic programming tasks described in this chapter, creating a Java servlet, JSP, or Java application that is a client to an enterprise bean is not very different from designing standard versions of these types of Java programs. This chapter assumes that you understand the basics of writing a Java servlet, a Java application, or a JSP file.
Except where noted, all of the code described in this chapter is taken from the example Java application named TransferApplication. This Java application and the other EJB clients available with the documentation example code are explained in Information about the examples described in the documentation.
To access and manipulate an enterprise bean in any of the Java-based EJB client types listed previously, the EJB client must do the following:
In addition, an EJB client can participate in the transactions associated with enterprise beans used by the client. For more information, see Managing transactions in an EJB client.
The Java client object request broker (ORB), which is automatically initialized in EJB clients, does not support dynamic download of implementation bytecode from the server to the client. As a result, all classes required by the EJB client at runtime must be available from the files and directories identified in the client's CLASSPATH environment variable. For information on the JAR files required by EJB clients, see Setting the CLASSPATH environment variable in the EJB server environment. You can install needed files on your client machine by doing a WebSphere Application Server installation on the machine. Select the Developer's Client Files option. You also need to make sure that the ioser and ioserx executable files are accessible on your client machine; these files are normally part of the Java install. If you are using a Windows System, make sure that EJB clients can locate the ioser.dll library file at run time. Figure 26 shows the import statements for the example Java application com.ibm.ejs.doc.client.TransferApplication. In addition to the required Java packages mentioned previously, the example application imports the com.ibm.ejs.doc.transfer package because the application communicates with a Transfer bean. The example application also imports the InsufficientFundsException class contained in the same package as the Account bean.
Figure 26. Code example: The import statements for the Java application TransferApplication
... import java.awt.*; import java.awt.event.*; import java.util.*; import java.rmi.* ... import javax.naming.*; import javax.ejb.*; import javax.rmi.PortableRemoteObject; ... import com.ibm.ejs.doc.account.InsufficientFundsException; import com.ibm.ejs.doc.transfer.*; ... public class TransferApplication extends Frame implements ActionListener, WindowListener { ... }
To create or find an instance of a bean's EJB object, the client must do the following:
The TransferApplication client contains one reference to a Transfer EJB object, which the application uses to invoke all of the methods on the Transfer bean. When using session beans in Java applications, it is a good idea to make the reference to the EJB object a class-level variable rather than a variable that is local to a method. This allows your EJB client to repeatedly invoke methods on the same EJB object rather than having to create a new object each time the client invokes a session bean method. As discussed in Threading issues, this approach is not recommended for servlets, which must be designed to handle multiple threads.
The example Transfer bean uses environment variables as discussed in Implementing the ejbCreate methods. The TransferApplication uses a resource bundle contained in the com.ibm.ejs.doc.client.ClientResourceBundle.class file. To initialize a JNDI name service, an EJB client must set the appropriate values for the following JNDI properties:
For example, the property value iiop://bankserver.mybank.com:9019 directs an EJB client to look for a name server on the host named bankserver.mybank.com listening on port 9019. The property value iiop://bankserver.mybank.com directs an EJB client to look for a name server on the host named bankserver.mybank.com at port number 900. The property value iiop:/// directs an EJB client to look for a name server on the local host listening on port 900. If not specified, this property defaults to the local host and port number 900, which is the same as specifying iiop:///. The port number used by the name service can be changed by using the administrative interface.
The createTransfer method of the TransferApplication can be called multiple times as explained in Handling an invalid EJB object for a session bean. However, after the InitialContext object is created once, it remains good for the life of the client session. Therefore, the code required to create the InitialContext object is placed within an if statement that determines if the reference to the InitialContext object is null. If the reference is null, the InitialContext object is created; otherwise, the reference can be reused on subsequent creations of the EJB object.
Figure 27. Code example: Creating the InitialContext object
... public class TransferApplication extends Frame implements ActionListener, WindowListener { ... private InitialContext ivjInitContext = null; private Transfer ivjTransfer = null; private ResourceBundle bundle = ResourceBundle.getBundle( "com.ibm.ejs.doc.client.ClientResourceBundle"); ... private String nameService = null; private String accountName = null; private String providerUrl = null; ... private Transfer createTransfer() { TransferHome transferHome = null; Transfer transfer = null; // Get the initial context if (ivjInitContext == null) { try { Properties properties = new Properties(); // Get location of name service properties.put(javax.naming.Context.PROVIDER_URL, bundle.getString("providerUrl")); // Get name of initial context factory properties.put(javax.naming.Context.INITIAL_CONTEXT_FACTORY, bundle.getString("nameService")); ... ivjInitContext = new InitialContext(properties); } catch (Exception e) { // Error getting the initial context ... } } ... // Look up the home interface using the JNDI name ... // Create a new Transfer object to return ... return transfer; }
The example TransferApplication gets the JNDI name of the Transfer bean from the ClientResourceBundle class. After an object is returned by the lookup method, the static method javax.rmi.PortableRemoteObject.narrow is used to obtain an EJB home object for the specified enterprise bean. The narrow method takes two parameters: the object to be narrowed and the class of the EJB home object to be returned by the narrow method. The object returned by the javax.rmi.PortableRemoteObject.narrow method is cast to the class associated with the home interface.
Figure 28. Code example: Creating the EJBHome object
private Transfer createTransfer() { TransferHome transferHome = null; Transfer transfer = null; // Get the initial context ... // Look up the home interface using the JNDI name try { java.lang.Object homeObject = ivjInitContext.lookup( bundle.getString("transferName")); transferHome = (TransferHome)javax.rmi.PortableRemoteObject.narrow( homeObject, TransferHome.class); } catch (Exception e) { // Error getting the home interface ... } ... // Create a new Transfer object to return ... return transfer; }
Figure 29. Code example: Creating the EJB object
private Transfer createTransfer() { TransferHome transferHome = null; Transfer transfer = null; // Get the initial context ... // Look up the home interface using the JNDI name ... // Create a new Transfer object to return try { transfer = transferHome.create(); } catch (Exception e) { // Error creating Transfer object ... } ... return transfer; }
An EJB client can determine if an EJB object is valid by placing all method invocations that use the reference inside of a try/catch block that specifically catches the java.rmi.NoSuchObjectException, in addition to any other exceptions that the method needs to handle. The EJB client can then invoke the code to handle this exception.
You determine how to handle an invalid EJB object. The example TransferApplication creates a new Transfer EJB object if the one it is currently using becomes invalid. The code to create a new EJB object when the old one becomes invalid is the same code used to create the original EJB object and is described in Creating and getting a reference to a bean's EJB object. For the example TransferApplication client, this code is contained in the createTransfer method.
Figure 30 shows the code used to create the new EJB object in the getBalance method of the example TransferApplication. The getBalance method contains the local boolean variable sessionGood, which is used to specify the validity of the EJB object referenced by the variable ivjTransfer. The sessionGood variable is also used to determine when to break out of the do-while loop. The sessionGood variable is initialized to false because the ivjTransfer can reference an invalid EJB object when the getBalance method is called. If the ivjTransfer reference is valid, the TransferApplication invokes the Transfer bean's getBalance method and returns the balance. If the ivjTransfer reference is invalid, the NoSuchObjectException is caught, the TransferApplication's createTransfer method is called to create a new Transfer EJB object reference, and the sessionGood variable is set to false so that the do-while loop is repeated with the new valid EJB object. To prevent an infinite loop, the sessionGood variable is set to true when any other exception is thrown.
Figure 30. Code example: Refreshing the EJB object reference for a session bean
private float getBalance(long acctId) throws NumberFormatException, RemoteException, FinderException { // Assume that the reference to the Transfer session bean is no good ... boolean sessionGood = false; float balance = 0.0f; do { try { // Attempt to get a balance for the specified account balance = ivjTransfer.getBalance(acctId); sessionGood = true; ... } catch(NoSuchObjectException ex) { createTransfer(); sessionGood = false; } catch(RemoteException ex) { // Server or connection problem ... } catch(NumberFormatException ex) { // Invalid account number ... } catch(FinderException ex) { // Invalid account number ... } } while(!sessionGood); return balance; }
You remove entity EJB objects only when you want to remove the information in the data source with which the entity EJB object is associated.
To remove an EJB object, invoke the remove method on the object. As discussed in Creating and getting a reference to a bean's EJB object, the TransferApplication contains only one reference to a Transfer EJB object that is created when the application is initialized.
Figure 31 shows how the example Transfer EJB object is removed in the TransferApplication in the killApp method. To parallel the creation of the Transfer EJB object when the TransferApplication is initialized, the application removes the final EJB object associated with ivjTransfer reference right before closing the application's GUI window. The killApp method closes the window by invoking the dispose method on itself.
Figure 31. Code example: Removing a session EJB object
... private void killApp() { try { ivjTransfer.remove(); this.dispose(); System.exit(0); } catch (Throwable ivjExc) { ... } }
Figure 32. Code example: Managing transactions in an EJB client
... import javax.transaction.*; ... // Use JNDI to locate the UserTransaction object Context initialContext = new InitialContext(); UserTransaction tranContext = ( UserTransaction)initialContext.lookup("java:comp/UserTransaction"); // Set the transaction timeout to 30 seconds tranContext.setTransactionTimeout(30); ... // Begin a transaction tranContext.begin(); // Perform transaction work invoking methods on enterprise bean references ... // Call for the transaction to commit tranContext.commit();