EJBs have two important but distinct characteristics:
These topics will be considered more extensively in the next chapter, but in the context of Web applications, the second characteristic is the most attractive. One type of EJB is particularly alluring: container-managed persistent entity beans. Because the EJB container automatically takes care of storing and retrieving a CMP bean's data, CMP beans can completely free a developer from having to write database access code. EJBs, and particularly CMP entity beans, bear a superficial resemblance to standard JavaBeans; after obtaining a bean (using a few mysterious lines of code), we use getter and setter methods to obtain and change the information in it. The container takes care of the rest. The benefit of having database access taken care of automatically by using CMP entity beans comes at a significant cost. This cost includes:
Most of these costs are incurred because EJB is principally a technology for building distributed components. The mechanism for invoking an object method remotely introduces several layers of indirection between a call from an application to an EJB. Although some significant optimizations can be made, this is true even when EJBs are actually local and use a local interface instead of the remote interface. Some of the development costs can be mitigated to a degree by using an IDE, such as Oracle JDeveloper. Wizards in JDeveloper allow you to create and deploy a CMP bean, based either on an existing database table or a table that it creates for you, almost effortlessly. It can be argued, however, that this is a pretty extreme way to avoid writing database access code, especially if you look behind the scenes and see what's really going on and how many class files are generated to implement a single entity bean. Although you will need to consider carefully whether CMP beans are appropriate for a production application, you may nonetheless find that using an automated tool to generate an EJB from an existing table can be a convenient way to prototype code. Creating a Container-Managed Persistent Entity Bean Using JDeveloperIn this section, we will extend the Web application we began earlier in this chapter by using an EJB to obtain the track information for a selected CD. Normally, we wouldn't mix data access techniques; this is just a demonstration for comparing the different techniques. Starting JDeveloperBecause JDeveloper does not provide an installation program, it is not available from the start menu or an icon on your desktop. Instead, after downloading and unzipping JDeveloper, you will need to locate the jdev\bin directory and run the executable jdevw.exe manually. You can do this at a DOS prompt by navigating to it using the Windows Explorer or by selecting Run from the Start menu. If you've installed JDeveloper in a directory c:\JDeveloper, the full path would be c:\JDeveloper\jdev\bin\jdevw.exe A more convenient alternative to starting JDeveloper manually is to create a shortcut for JDeveloper on your desktop. Do this by right-clicking on an empty area of your desktop, selecting New | Shortcut, and entering the full path and filename for jdevw.exe (or press Browse and locate it that way). You can then start JDeveloper by double clicking on this shortcut. Create a Workspace and ProjectAfter JDeveloper starts, the System Navigator will appear in the top left pane of the main window. To create a workspace and project for developing the EJB:
Create a Connection to the DatabaseWe will be creating an EJB based on an existing table, the CD_COLLECTION table. To enable JDeveloper to connect to the database and locate this table, we will need to ensure that JDeveloper has a working database connection. At the bottom of the System Navigator tree:
Create the CMP Entity BeanWe will use a wizard to create the EJB. As you will see, we need only to indicate the type of bean, CMP based on a table, and identify the table. JDeveloper will do the rest. In the System Navigator:
Test the Entity Bean Using the Embedded OC4JJDeveloper can automatically create a sample application to test the EJB. We'll first test it using the OC4J container embedded in the JDeveloper IDE. First, start the bean:
The log window will show that the embedded OC4J server has been started and that the bean has been deployed. Next, we'll create a test application.
This will create a small class that will include a main() method to exercise the Cd_collection bean. To run the sample application:
The output, listing all the records in the CD_COLLECTION table, will display in the log window. Create a Connection for the Standalone OC4J InstanceTo test the application and bean on an application server in this case, on a standalone instance of OC4J we first need to create a connection for the application server:
Deploying the EJBThe first step in deploying an EJB is to create a deployment profile that establishes some basic settings:
Once we've created the deployment profile, we can deploy the bean as follows:
Testing the EJB on the Standalone OC4J ServerTo test the EJB on the standalone server, we can generate another test application:
This creates a new client application, Cd_collectionClient1.java. Locate this under the CDBean project in the System Navigator.
This sample application should produce the same results as the first one, listing all the records in the CD_COLLECTION table. In the next section, we'll use some of the code produced to access the EJB from our Servlet/JSP application. Using a CMP Entity Bean in a Web ApplicationThere are several ways that a CMP entity bean can be incorporated into a Web application.
Of these alternatives, the third is clearly the most appropriate for our CD Web application. In the example so far, we've created one JavaBean, a heavyweight one that has the logic in it to access the database and populate itself. Similarly here, we could create a heavyweight JavaBean that calls the EJB and populates itself. In a real application, we would want to be consistent in our choice. Here, to provide variety in the examples, we'll use a lightweight JavaBean. The controller class will call the EJB and populate the JavaBean before forwarding the request to a JSP, which will display the information contained in the JavaBean. The CDInfoControllerThe CDInfoController class contains the logic for obtaining a CMP bean containing the CD information we want. First are the imports, some constants, and instance variables. import java.io.*; import java.util.*; import javax.servlet.*; import javax.servlet.http.*; import java.util.Hashtable; import javax.naming.Context; import javax.naming.InitialContext; import mypackage1.Cd_collection; import mypackage1.Cd_collectionHome; import mypackage1.Cd_collectionPK; import java.util.Collection; import java.util.Iterator; import java.lang.Long; public class CDInfoController extends HttpController { private static final String CD_SELECTED = "CDSELECTED"; private static final String ARTIST_NAME = "ARTISTNAME"; private static final String CD_BEAN = "CDBean"; private static final String JSP_PAGE = "/CDInfo.jsp"; private static final String ERROR_PAGE = "/NotFound.jsp"; private String artistName; private long cdid; public void dispatch(HttpServletRequest request, HttpServletResponse response, ServletContext servletContext) throws ServletException, IOException { Cd_collection cdBean = null; CDInfoBean cdinfo = new CDInfoBean(); parseCDSelected(request); The literal constants are the names of the JPS files and the names of the attributes that we use to communicate with them. We also instantiate the Java-Bean (CDInfoBean not shown here) that we will use to store the CD information. The variables artistName and cdid are the information that we need to obtain from the attribute that was set when the user pressed the Submit button for a particular CD. Remember that these two values were concatenated into a single string and set as the NAME attribute of a session parameter because of limitations in the way an HTML Submit button is implemented in certain browsers. The logic for obtaining information from the CDSELECTED attribute is not straightforward because we don't know the precise name of the attribute. We only know that it begins with CDSELECTED, as in the following example: CDSELECTED=112;ARTISTNAME=Richard Thompson The helper method, parseCDSelected(), looks through the session's attributes, locates this string and parses it, and sets the class's cdid and artistName variables. We interrupt the presentation of the dispatch() method to present the code for this helper method. void parseCDSelected(HttpServletRequest request) { Enumeration paramNames = request.getParameterNames(); while(paramNames.hasMoreElements()) { String paramName = (String) paramNames.nextElement(); System.out.println("-- " + paramName); if(paramName.startsWith(CD_SELECTED)) { StringTokenizer st= new StringTokenizer(paramName, ";=", true); if(st.countTokens()>=7) { if( st.nextToken().equals(CD_SELECTED) && st.nextToken().equals("=")) { cdid=Long.parseLong(st.nextToken().trim()); } if( st.nextToken().equals(";") && st.nextToken().equals(ARTIST_NAME) && st.nextToken().equals("=") ) { artistName=st.nextToken().trim(); } } } } } Note that this method is fairly strict in enforcing the format of the attributes; this is useful for debugging purposes. Normally, it would be sufficient simply to ensure that the right number of tokens are present and retrieve the desired ones, throwing the rest away. Calling the EJBTo use an EJB, we need to obtain two objects:
To find the home object, we use JNDI, the Java API for naming and directory services. The following code, lifted straight out of the sample client application Cd_collection1.java that JDeveloper created for us, sets a number of parameters to obtain an initial context for the JNDI search. (It uses a hashtable that it builds dynamically, but this is equivalent to using a properties file, which is what we would do in a production environment.) The code then uses JNDI to obtain the home object for our EJB, Cd_collection. Here, we are once again looking at the code for the dispatch() method: try { // source from sample application code Hashtable env = new Hashtable(); env.put(Context.INITIAL_CONTEXT_FACTORY, "com.evermind.server.rmi.RMIInitialContextFactory"); env.put(Context.SECURITY_PRINCIPAL, "admin"); env.put(Context.SECURITY_CREDENTIALS, "adminpwd"); env.put(Context.PROVIDER_URL, "ormi://noizmaker/CDBean"); Context ctx = new InitialContext(env); Cd_collectionHome cd_collectionHome = (Cd_collectionHome)ctx.lookup("Cd_collection"); // end source from sample code Once we have the home object, we can obtain our EJB object by using the CDID and the findByPrimaryKey() finder method that obtains the corresponding bean. To do this, we need to create a primary key. In theory, we can use a Java wrapper class as the primary key (and need to use a custom Java class only if it is something more complex, such as a combination of columns) but JDeveloper by default creates a custom primary key class. In this case, it wraps the long value representing the CDID. To use the primary key class it has created, Cd_collectionPK(), we instantiate it by calling its constructor, which takes a long value. We then use this key and call the finder method that obtains the corresponding bean. Cd_collectionPK pk = new Cd_collectionPK(cdid); cd_collection = cd_collectionHome.findByPrimaryKey(pk); We can now use this bean by calling its getter and setter methods. Here, we only use it to obtain information to populate a JavaBean, CDBeanInfo: // fill JavaBean cdinfo.setAlbumTitle(cdBean.getAlbum_title()); cdinfo.setReleaseDate(cdBean.getRelease_date()); cdinfo.setLabel(cdBean.getLabel()); cdinfo.setArtistID(cdBean.getArtist_id()); cdinfo.setCDID(cdBean.getCd_id()); } catch(Exception e) { System.out.println("Caught: " + e); } Once we've successfully put the information in a JavaBean, we can set this bean and the artist's name as request attributes. We then obtain and call a request dispatcher; note that we call an error page instead of the regular JSP if we are unable to obtain the CD information. RequestDispatcher dispatcher=null; if(cd_collection!=null) { request.setAttribute(ARTIST_NAME, artistName); request.setAttribute(CD_BEAN, cdinfo); dispatcher = servletContext.getRequestDispatcher(JSP_PAGE); } else { dispatcher = servletContext.getRequestDispatcher(ERROR_PAGE); } dispatcher.forward(request, response); This is the JSP file for displaying the CD information: <!DOCTYPE HTML PUBLIC "-//W3C//DTD 4.0 Transitional//EN"> <HTML> <HEAD> <TITLE>CD Info</TITLE> </HEAD> <BODY> <H2>CD Info:</H2> <jsp:useBean scope="request" /> Album title: <%= CDBean.getAlbumTitle() %><BR> Artist name: <%= request.getAttribute("ARTISTNAME") %> <BR> Label: <%= CDBean.getLabel() %><BR> Release date: <%= CDBean.getReleaseDate() %><BR> <FORM ACTION="/servlet/MusicServlet" METHOD="POST"> <INPUT TYPE="SUBMIT" NAME="PROCEED" VALUE="Continue"> <INPUT TYPE="HIDDEN" NAME="REQUESTPAGE" VALUE="STARTPAGE"> </FORM> </BODY> </HTML> Add the CDInfoController.java and CDInfoBean.java files to the default-web-app\WEB-INF\classes directory and the JSP file to the default-web-app directory, and run the MusicResults application again. This time, when we select a CD, the information for the selected CD will appear in the new CD Info page. |