.
.
Core Java Technologies Technical Tips
.
   View this issue as simple text August 25, 2003    

In this Issue

Welcome to the Enterprise Java Technologies Tech Tips for August 25, 2003. Here you'll get tips on using enterprise Java technologies and APIs, such as those in Java 2 Platform, Enterprise Edition (J2EE).

This issue covers:

.Application Initialization Using Listeners
.EJB QL

These tips were developed using Java 2 SDK, Standard Edition, v 1.4 and Java 2 SDK, Enterprise Edition, v 1.3.1 (Reference Implementation).

This issue of the Tech Tips is written by Mark Johnson, president of elucify technical communications, and co-author of Designing Enterprise Applications with the J2EE Platform, 2nd Edition. Mark Johnson runs an open forum for discussion of the tips.

You can download the sample archive for these tips. The context root for the application is ttaug2003, and the index.html welcome file indicates how to use the sample code. Any use of this code and/or information below is subject to the license terms.

.

APPLICATION INITIALIZATION USING LISTENERS

The June 26, 2003 Tech Tip, "Servlet Life Cycle Listeners" explained how to use servlet life cycle event listeners to execute application-specific code at various points in a servlet life cycle. That tip also mentioned that servlet life cycle event listeners can be used to initialize application components when an application is deployed. The following tip presents an example of using a ServletContextListener to create a network of entity beans when a Web application is deployed.

Servlet Life Cycle Listeners--Review

Life cycle listeners are event listener classes that receive events from a servlet container. The servlet container notifies listeners when specific life cycle events occur. These events are:

  • Creation or destruction of a servlet context or HTTP session
  • Creation, modification, or removal of a servlet context attribute or an HTTP session attribute
  • Activation or passivation of an HTTP session
  • Notification to an object that has been bound to or unbound from an HTTP session attribute (in versions that implement the servlet specification above 2.3)

Each Web application has a single ServletContext object. This object is shared between all servlets and JSP pages in the application. (Distributed Web applications have one ServletContext for each Web application, for each Java virtual machine*.) When a Web application is deployed, the Web container initializes its ServletContext object. If the Web application's deployment descriptor declares any ServletContextListeners, the container calls each listener's contextInitialized method, in the order that the listeners are declared. This event occurs only once in the servlet's life cycle: before any filter or servlet in the Web application is initialized. As a result, the method ServletContextListener.contextInitialized can be used to initialize application components when the application is deployed.

Note that Listeners must not use the interface javax.transaction.UserTransaction to demarcate transactions.

Sample Code

The Tech Tip "Finder Methods and EJB QL" follows this tip. It shows how to create finder methods to identify collections of entity beans. The example used in that tip requires entity beans for the finder methods to find. So there must be a way to create a network of entity beans when the application is deployed. And that way is provided through a ServletContextListener. Specifically, the sample code provided with the tip includes a class, LoadDataSCL, that implements a ServletContextListener whose contextInitialized method creates a network of entity beans. The method creates the beans based on the contents of an XML file that is deployed in the Web application archive (WAR file). Here is the contextInitialized method of class LoadDataSCL:

   public void contextInitialized(
                             ServletContextEvent sce) {
      ServletContext sc = sce.getServletContext();
      
      System.out.println(
         "contextInitialized: Creating entity beans.");

      DataLoader dl = new DataLoader();
      InputStream is = sc.getResourceAsStream(
                                    "/persondata.xml");
      dl.load(is);

      System.out.println(
          "contextInitialized: Entity beans created.");
   }

The method receives a single argument of type ServletContextEvent. This event contains a single method, getServletContext, that returns a reference to the servlet context that is being initialized. The DataLoader object creates local Person, Address, and PhoneNumber entity beans using data read from an XML-formatted InputStream. The input stream comes from the call to getResourceAsStream -- this opens a stream to an XML file packaged in the application archive.

When the application is deployed, the servlet container initializes its ServletContext object. It then creates an instance of the LoadDataSCL class, and passes a ServletContextEvent object to its contextInitialized method. The method then creates the network of entity beans defined in the XML file. If you deploy the application on the Reference Implementation, it should produce the following output in the server log file:

   contextInitialized: Creating entity beans.
   INFO: Document parsed
   contextInitialized: Entity beans created.
   Created Context:/ttaug2003
   Application TTAug2003 deployed.

Note that the initialization occurs before the application deployment is complete. Using a listener to initialize data ensures that the objects being created or initialized exist before any Web requests are serviced.

This technique is not only useful for creating networks of entity beans. Any resource that might be shared between Web components, such as database connections, JMS sessions, or Connector references, could be initialized in a servlet context listener. References to the initialized or created resources could be stored in context attributes for later use by servlets and JSP pages. Keep in mind that it's important to synchronize access to Web components. That's because context attributes are shared between Web components. Without proper synchronization, multiple servlet instances or JSP pages might try simultaneously to access the shared resource. This would cause race conditions and result in application failure.

Finally, you can use other types of listeners to create resources for Web applications. For example, you might store a reference to a stateful session bean (representing a shopping cart, for instance) in an HTTP session attribute. The same session bean reference could then be used to service multiple Web requests in the same session. You could acquire this stateful session bean reference and store it in the HTTP session using an HTTP session listener class. The class would need to properly handle session migration and passivation/activation if the specific application server might perform these operations.

.
.

EJB QL

Finder methods are entity bean home interface methods that find instances of entity beans or collections of entity beans. Application and component developers define the criteria to use in finding the instances. For example, a component developer might define an Account home interface method called findDelinquentAccounts. This method returns a collection of references to all Account beans representing accounts that are more than 90 days late.

Before Enterprise JavaBeans (EJB) version 2.0, it could be difficult to create finder methods. However with the introduction of Query Language (EJB QL) in the EJB specification version 2.0, it's now easier to create finder methods.

EJB QL is an object-oriented query language based on (and similar to) standard SQL 92. This tip explains how you can use EBJ-QL to implement finder methods in EJB 2.0 and above. It also provides some examples of finders using the entity beans created by the initializing loader from the previous tip "Application Initialization Using Listeners."

Finders And Queries

Before EJB 2.0, there were two options for creating finder methods:

  • Use bean-managed persistence (BMP), and implement ejbFind methods manually.

  • Use container-managed persistence (CMP), and define finder implementations using a vendor-specific query language.

The first option had the advantage that it could be done relatively portably. Unfortunately, it also meant a lot of work, and missed the benefits of CMP. The second option was relatively easier to implement, but sacrificed portability.

Finder methods in EJB 2.0 and above are portable and easy: in fact, you don't have to write the finder methods at all. Creating a finder method in EJB 2.0 involves two steps.

  1. Define the finder method signature in the home interface of an entity bean, as shown in the following excerpt from the sample code. Here the finder method signature is defined in the PersonLocalHome interface of the PersonLocal entity bean:
          public interface PersonLocalHome 
             extends EJBLocalHome {
                 ...
             public Collection findAll() 
                throws FinderException;
                 ...
          };
    
  2. Second, add a <query> element to the deployment descriptor for the enterprise bean. A excerpt from the deployment descriptor for the PersonLocal entity bean appears below:
          <query>
            <description>
               Return all Person objects
            </description>
            <query-method>
              <method-name>findAll</method-name>
              <method-params />
            </query-method>
            <ejb-ql>
              SELECT OBJECT(p) FROM Person AS p
            </ejb-ql>
          </query>
    

The <query> has a <description> element that documents what the query does. The <query-method> element defines the finder method name (in this case, findAll) defined in the home interface for the entity bean. A list of <method-param> elements provides the method parameter types. The name of a method plus an ordered list of types uniquely identifies a method signature. Finally, an <ejb-ql> element defines an EJB QL query that identifies which entity beans should be returned by the finder. In this case, all Person objects are returned.

At deploy time, the deployment tools use the EJB QL query to write (or otherwise implement) the finder method for you. Because the semantics of EBJ-QL are specified in the EJB specification, EJB QL queries are portable across application servers.

Let's take a closer look at finder methods and EJB QL queries.

Writing Finder Method Declarations

As mentioned earlier, the only work involved in creating a finder method is declaring its method signature in the entity bean's home interface. The method itself is implemented by the container. Finder methods have some restrictions and caveats:

  • Finder method names must begin with the string "find".

  • A finder method must be public.

  • Every finder method throws FinderException.

  • A remote finder method throws RemoteException. However a local finder method must not do so.

  • The finder method return value is either the component interface type, or a java.util.Collection object of the component interface type. If the query could even potentially return more than one value, its return value must be java.util.Collection.

  • A home interface must contain a method findByPrimaryKey(primaryKeyType), which must exist in the home interface, and must not be overloaded. The type primaryKeyType is the type of the entity's primary key, and the method returns the component interface type.

After you define the finder signatures, the next step is to add the <query> elements to the deployment descriptor for the entity bean. The <query> element is nested inside the <entity> element that defines the corresponding entity bean.

Understanding EJB QL

An EJB QL query selects values, objects, and collections of objects from the abstract persistence schema of entity beans. The abstract persistence schema is the portable schema representation of entity beans, their CMP fields, and the CMR fields that relate them to one another. An EJB QL query is a query against the abstract schema. Container and deployment tools map the abstract schema and queries against it into real schema and queries for a specific platform implementation.

An EJB QL query is composed of:

  • A SELECT clause that determines what the query returns
  • A FROM clause that indicates where the data comes from
  • A WHERE clause that determine the criteria for object selection

The following examples show and explain the finder methods and <query> elements in the sample code. The finder methods are declared in the PersonLocalHome interface of the PersonLocal entity bean.

Finding an object by its Primary Key

Every object must have a findByPrimaryKey method. The signature for this method in PersonLocalHome is:

   public PersonLocal findByPrimaryKey(String key)
      throws FinderException;

There is no corresponding EJB QL because this method is always implemented by the container for every entity bean.

Finding all objects

Many entity beans provide a findAll method that returns a collection of component interface types. The finder method declaration in the sample code is:

   public Collection findAll() throws FinderException;

The corresponding EJB QL for this finder, as described earlier, is:

   SELECT OBJECT(p) FROM Person AS p

This query selects objects from Person. The FROM clause selects Person objects, and "nicknames" each object p. That nickname is converted to an object type in the select clause, using OBJECT(p). Remember that finder methods can return only component interfaces or collections.

Using NULL to determine if a value exists

In the sample code's abstract schema, a Person can have zero or one workAddress objects. If it has zero, the value of Person.workAddress is NULL. The signature for the finder method to find users with no workAddress is:

   public Collection findWithNoWorkAddress()
     throws FinderException;

The corresponding EJB QL in the deployment descriptor is:

   SELECT OBJECT(p)
   FROM Person AS p
   WHERE p.workAddress IS NULL

This query is similar to findAll's, except that the results are limited to people whose "workAddress IS NULL". The condition "workAddress IS NULL" occurs when the Person has no workAddress. The keyword NULL can also be used in WHERE clauses to test whether a cmp-field is set (for example, "WHERE p.middlename IS NULL").

Using EMPTY to see if a collection has no members

The relationship between Person and PhoneNumber is one-to-many in the abstract persistence schema. The following PersonLocalHome method finds all Person objects with no Phone:

   public Collection findWithNoPhone() 
      throws FinderException;

The EJB QL for this finder is:

   SELECT OBJECT(p) FROM Person AS p
   WHERE p.phones IS EMPTY

Because p.phones is a collection, IS EMPTY is used instead of IS NULL. The EJB QL NOT operator could be used here to find all Persons that do have Phones. To do that, change the WHERE clause to WHERE p.phones IS NOT EMPTY.

Passing arguments to queries

Queries can take positional parameters in certain places (see the EJB 2.0 specification for details). The parameters appear in the queries in the form "?1", "?2", and so on. Each number indicates the position of the parameter in the parameter list of the finder signature. The index origin of the parameters is 1. (In other words, there is no "?0".)

Each Person in the database has a gender, with a value of M or F. The finder to get all Persons by gender is:

   public Collection findGender(String gender)
      throws FinderException;

And its <query> element is:

   <query>
     <description>Return persons by gender</description>
     <query-method>
       <method-name>findGender</method-name>
       <method-params>
         <method-param>java.lang.String</method-param>
       </method-params>
     </query-method>
     <ejb-ql>SELECT OBJECT(p) FROM Person AS p
       WHERE p.gender = ?1</ejb-ql>
   </query>

Notice the <method-param> element. This element indicates that the method has a single String argument. Notice too that the WHERE clause contains WHERE p.gender = ?1. The parameter ?1 is replaced by the value of the String argument when the query is executed.

This tip provides just an introduction to EJB QL for creating finder methods. EJB QL can also be used for creating select methods, which will be explored in a later edition of the Enterprise Java Technologies Tech Tips. For more in-depth information about EJB QL see the publication Applying Enterprise JavaBeans.

.
.

RUNNING THE SAMPLE CODE

Download the sample archive for these tips. The application's context root is ttaug2003. The downloaded EAR file also contains the complete source code for the sample.

To run the sample program, follow these steps:

  1. Be sure that both the j2ee server and database server are running. For the J2EE Reference Implementation, execute both:
         j2ee -verbose
    
    and
         cloudscape -start
    
  2. Download the ear file as described above.

  3. Deploy the ear file to your server. You can deploy the application archive (ttaug2003.ear) in the J2EE Reference Implementation using the deploytool program:
        $J2EE_HOME/deploytool -deploy ttaug2003.ear localhost
    
    Replace localhost with the name of the host on which the server is installed. For a standard installation on a single machine, the hostname typically (and literally) is localhost.

  4. Access the application at http://localhost:8000/ttaug2003.

For a J2EE-compliant implementation other than the Reference Implementation, use your J2EE product's deployment tools to deploy the application on your platform.

See the index.html welcome file for instructions on running the application.

When you run the application, you should see a page that includes the following:

figure 5

Choose one or more of the highlighted functions to run the associated EJB QL. For example, if you select:

   List Persons with no phone

It runs the EJB QL for the finder method findWithNoPhone():

   SELECT OBJECT(p) FROM Person AS p
   WHERE p.phones IS EMPTY   

You should see the result:

figure 5

.
.
.

Reader Feedback

  Very worth reading    Worth reading    Not worth reading 

If you have other comments or ideas for future technical tips, please type them here:

 

Have a question about Java programming? Use Java Online Support.

.
.

IMPORTANT: Please read our Terms of Use, Privacy, and Licensing policies:
http://www.sun.com/share/text/termsofuse.html
http://www.sun.com/privacy/
http://developer.java.sun.com/berkeley_license.html


Comments? Send your feedback on the Enterprise Java Technologies Tech Tips to: http://developers.sun.com/contact/feedback.jsp?category=sdn

Subscribe to other Java developer Tech Tips:

- Core Java Technologies Tech Tips. Get tips on using core Java technologies and APIs, such as those in the Java 2 Platform, Standard Edition (J2SE).
- Wireless Developer Tech Tips. Get tips on using wireless Java technologies and APIs, such as those in the Java 2 Platform, Micro Edition (J2ME).

To subscribe to these and other JDC publications:
- Go to the JDC Newsletters and Publications page, choose the newsletters you want to subscribe to and click "Update".
- To unsubscribe, go to the subscriptions page, uncheck the appropriate checkbox, and click "Update".


ARCHIVES: You'll find the Enterprise Java Technologies Tech Tips archives at:
http://developer.java.sun.com/developer/EJTechTips/


Copyright 2003 Sun Microsystems, Inc. All rights reserved. 4150 Network Circle, Santa Clara, CA 95054 USA.

This document is protected by copyright.

Trademark Information: http://www.sun.com/suntrademarks/
Java, J2SE, J2EE, J2ME, and all Java-based marks are trademarks or registered trademarks of Sun Microsystems, Inc. in the United States and other countries.


* As used in this document, the terms "Java virtual machine" or "JVM" mean a virtual machine for the Java platform.

Sun Microsystems, Inc.
.
.