home | O'Reilly's CD bookshelfs | FreeBSD | Linux | Cisco | Cisco Exam  


CONTENTS

Chapter 9. EJB 1.1 CMP

9.1 A Note for EJB 2.0 Readers

Container-managed persistence has undergone a dramatic change in EJB 2.0, which is not backward compatible with EJB 1.1. For that reason, EJB 2.0 vendors must support both EJB 2.0's container-managed persistence model and EJB 1.1's container-managed persistence model. The EJB 1.1 model is supported purely for backward compatibility, so that application developers can migrate their existing applications to the new EJB 2.0 platform as painlessly as possible. It's expected that all new entity beans and new applications will use EJB 2.0 container-managed persistence, not the EJB 1.1 version. Although EJB 1.1 container-managed persistence is covered in this book, avoid it unless you maintain a legacy EJB 1.1 system. EJB 2.0 container-managed persistence is covered in Chapter 6 through Chapter 8.

EJB 1.1 container-managed persistence is limited in several ways. For example, EJB 1.1 CMP beans can have only remote component interfaces; they are not allowed to have local or local home interfaces. Most importantly, EJB 1.1 container-managed entity beans do not support relationships. Other subtle differences also make EJB 1.1 CMP more limiting than EJB 2.0 CMP. For example, the ejbCreate() and ejbPostCreate() methods in EJB 1.1 do not support the < METHOD-NAME> suffix allowed in EJB 2.0, which makes method overloading more difficult. EJB 2.0 CMP is a major improvement over CMP 1.1 and should be used if at all possible.

9.2 Overview for EJB 1.1 Readers

The following overview of EJB 1.1 container-managed persistence is pretty much duplicated in Chapter 6, but for EJB 1.1 readers who have not read Chapter 6, the overview is important to understanding the context of entity beans and container-managed persistence.

In Chapter 4, we started developing some simple enterprise beans, skipping over a lot of the details about developing enterprise beans. In this chapter, we'll take a thorough look at the process of developing entity beans.

Entity beans model business concepts that can be expressed as nouns. This is a rule of thumb rather than a requirement, but it helps in determining when a business concept is a candidate for implementation as an entity bean. In grammar school you learned that nouns are words that describe a person, place, or thing. The concepts of person and place are fairly obvious: a person EJB might represent a customer or passenger, and a place EJB might represent a city or port-of-call. Similarly, entity beans often represent things: real-world objects like ships, customers, and so on. An entity bean can even represent a fairly abstract thing, such as a reservation. Entity beans describe both the state and behavior of real-world objects and allow developers to encapsulate the data and business rules associated with specific concepts; a Ship EJB encapsulates the data and business rules associated with a ship, and so on. This makes it possible for data associated with a concept to be manipulated consistently and safely.

In Titan's cruise ship business, we can identify hundreds of business concepts that are nouns and therefore could conceivably be modeled by entity beans. We've already seen a simple Cabin EJB in Chapter 4, and we'll develop a Ship EJB in this chapter. Titan could clearly make use of a Customer EJB, a Cruise EJB, a Reservation EJB, and many others. Each of these business concepts represents data that needs to be tracked and possibly manipulated.

Entities really represent data in the database, so changes to an entity bean result in changes to the database. There are many advantages to using entity beans instead of accessing the database directly. Utilizing entity beans to objectify data provides programmers with a simpler mechanism for accessing and changing data. It is much easier, for example, to change a ship's name by calling ShipRemote.setName() than by executing an SQL command against the database. In addition, objectifying the data using entity beans provides for more software reuse. Once an entity bean has been defined, its definition can be used throughout Titan's system in a consistent manner. The concept of a ship, for example, is used in many areas of Titan's business, including booking, scheduling, and marketing. A Ship EJB provides Titan with one complete way of accessing ship information, and thus it ensures that access to the information is consistent and simple. Representing data as entity beans makes development easier and more cost-effective.

When a new EJB is created, a new record must be inserted into the database and a bean instance must be associated with that data. As the EJB is used and its state changes, these changes must be synchronized with the data in the database: entries must be inserted, updated, and removed. The process of coordinating the data represented by a bean instance with the database is called persistence.

There are two basic types of entity beans, and they are distinguished by how they manage persistence. Container-managed persistence beans have their persistence automatically managed by the EJB container. The container knows how a bean instance's persistence fields and relationships map to the database and automatically takes care of inserting, updating, and deleting the data associated with entities in the database. Entity beans using bean-managed persistence do all this work manually: the bean developer must write the code to manipulate the database. The EJB container tells the bean instance when it is safe to insert, update, and delete its data from the database, but it provides no other help. The bean instance does all the persistence work itself. Bean-managed persistence is covered in Chapter 10.

9.3 Container-Managed Persistence

When you deploy an EJB 1.1 CMP entity bean, you must identify which fields in the entity will be managed by the container and how they map to the database. The container then automatically generates the logic necessary to save the bean instance's state.

Fields that are mapped to the database are called container-managed fields—EJB 1.1 doesn't support relationship fields, as EJB 2.0 does. Container-managed fields can be any Java primitive type or serializable object. Most beans use Java primitive types when persisting to a relational database, since it's easier to map Java primitives to relational data types.

EJB 1.1 also allows references to other beans to be container-managed fields. For this to work, the EJB vendor must support converting bean references (remote or home interface types) from remote references to something that can be persisted in the database and converted back to a remote reference automatically. Vendors will normally convert remote references to primary keys, Handle or HomeHandle objects, or some other proprietary pointer type that can be used to preserve the bean reference in the database. The container will manage this conversion from remote reference to a persistent pointer and back automatically. This feature was abandoned in EJB 2.0 CMP in favor of container-managed relationship fields.

The advantage of container-managed persistence is that the bean can be defined independently of the database used to store its state. Container-managed beans can take advantage of both relational and object-oriented databases. The bean state is defined independently, which makes the bean reusable and more flexible.

The disadvantage of container-managed beans is that they require sophisticated mapping tools to define how the beans' fields map to the database. In some cases, this is a simple matter of mapping each field in the bean instance to a column in the database or of serializing the bean to a file. In other cases, it is more difficult. The state of some beans, for example, may be defined in terms of a complex relational database join or mapped to some kind of legacy system such as CICS or IMS.

In this chapter we will create a new container-managed entity bean, the Ship EJB, which we will examine in detail. A Ship EJB is also used in both Chapter 7, when discussing complex relationships in EJB 2.0, and Chapter 10, when discussing bean-managed persistence. When you are done with this chapter you may want compare the Ship EJB developed here with the ones created in Chapter 7 and Chapter 10.

Let's start by thinking about what we're trying to do. An enormous amount of data would go into a complete description of a ship, but for our purposes we will limit the scope of the data to a small set of information. For now, we can say that a ship has the following characteristics or attributes: its name, passenger capacity, and tonnage (i.e., size). The Ship EJB will encapsulate this data, and we'll need to create a SHIP table in our database to hold the data.

Here is the definition for the SHIP table expressed in standard SQL:

CREATE TABLE SHIP (ID INT PRIMARY KEY NOT NULL, NAME CHAR(30), CAPACITY INT, 
TONNAGE DECIMAL(8,2))

When defining any bean, we start by coding the remote interfaces. This focuses our attention on the most important aspect of the bean: its business purpose. Once we have defined the interfaces, we can start working on the actual bean definition.

9.3.1 The Remote Interface

We will need a remote interface for the Ship EJB. This interface defines the business methods clients will use to interact with the bean. When defining the remote interface, we will take into account all the different areas in Titan's system that may want to use the ship concept. Here is the remote interface, ShipRemote, for the Ship EJB:

package com.titan.ship;

import javax.ejb.EJBObject;
import java.rmi.RemoteException;

public interface ShipRemote extends javax.ejb.EJBObject {
    public String getName() throws RemoteException;
    public void setName(String name) throws RemoteException;
    public void setCapacity(int cap) throws RemoteException;
    public int getCapacity() throws RemoteException;
    public double getTonnage() throws RemoteException;
    public void setTonnage(double tons) throws RemoteException;
}

9.3.2 The Remote Home Interface

The remote home interface of any entity bean is used to create, locate, and remove objects from EJB systems. Each entity bean type has its own home interface. The home interface defines two basic kinds of methods: zero or more create methods and one or more find methods.[1] The create methods act like remote constructors and define how new Ship EJBs are created. (In our home interface, we provide only a single create method.) The find method is used to locate a specific Ship or Ships.

The following code contains the complete definition of the ShipHomeRemote interface.

package com.titan.ship;

import javax.ejb.EJBHome;
import javax.ejb.CreateException;
import javax.ejb.FinderException;
import java.rmi.RemoteException;
import java.util.Enumeration;

public interface ShipHomeRemote extends javax.ejb.EJBHome {

    public ShipRemote create(Integer id, String name, 
        int capacity, double tonnage)
        throws RemoteException,CreateException;
    public ShipRemote create(Integer id, String name)
        throws RemoteException,CreateException;
    public ShipRemote findByPrimaryKey(Integer primaryKey)
        throws FinderException, RemoteException;
    public Enumeration findByCapacity(int capacity)
        throws FinderException, RemoteException;
}

Enterprise JavaBeans specifies that create methods in the home interface must throw the javax.ejb.CreateException. In the case of container-managed persistence, the container needs a common exception for communicating problems experienced during the create process.

9.3.2.1 The find methods

EJB 1.1 CMP supports only find methods, not EJB 2.0's select methods. In addition, only the remote home interface supports find methods; EJB 1.1 entity beans do not support local component interfaces.

With EJB 1.1 container-managed persistence, implementations of the find methods are generated automatically at deployment time. Different EJB-container vendors employ different strategies for defining how the find methods work. Regardless of the implementation, when you deploy the bean, you'll need to do some work to define the rules of the find method. findByPrimaryKey() is a standard method that all home interfaces for entity beans must support. This method locates beans based on the attributes of the primary key. In the case of the Ship EJB, the primary key is the Integer class, which maps to the id field of the ShipBean. With relational databases, the primary key attributes usually map to a primary key in a table. In the ShipBean class, for example, the id attribute maps to the ID primary key column in the SHIP table. In an object-oriented database, the primary key's attributes might point to some other unique identifier.

EJB 1.1 allows you to specify other find methods in the home interface, in addition to findByPrimaryKey(). All find methods must have names that match the pattern find<SUFFIX>(). So, for example, if we were to include a find method based on the Ship EJB's capacity, it might be called findByCapacity(int capacity). In container-managed persistence, any find method included in the home interface must be explained to the container. In other words, the deployer needs to define how the find method should work in terms the container understands. This is done at deployment time, using the vendor's deployment tools and syntax specific to the vendor.

Find methods return either the remote interface type appropriate for that bean, or an instance of the java.util.Enumeration or java.util.Collection type. Unlike EJB 2.0 CMP, EJB 1.1 CMP doesn't support java.util.Set as a return type for find methods.

Specifying a remote interface type indicates that the method locates only one bean. The findByPrimaryKey() method obviously returns a single remote reference because there is a one-to-one relationship between a primary key's value and an entity. The findByCapacity(int capacity) method, however, could return several remote references, one for every ship that has a capacity equal to the parameter capacity. To be able to return more than one remote reference, the find method must return either the Enumeration or Collection type. Enterprise JavaBeans specifies that any find method used in a home interface must throw the javax.ejb.FinderException. Find methods that return a single remote reference throw a FinderException if an application error occurs and a javax.ejb.ObjectNotFoundException if a matching bean cannot be found. The ObjectNotFoundException is a subtype of FinderException and is thrown only by find methods that return single remote references. Find methods that return an Enumeration or Collection type (multi-entity finders) return an empty collection (not a null reference) if no matching beans can be found and throw a FinderException if an application error occurs.

How find methods are mapped to the database for container-managed persistence is not defined in the EJB 1.1 specification—it is vendor specific. Consult the documentation provided by your EJB vendor to determine how find methods are defined at deployment time. Unlike EJB 2.0 CMP, there is no standard query language for expressing the behavior of find methods at runtime.

9.3.3 The Primary Key

A primary key is an object that uniquely identifies an entity bean according to the bean type, home interface, and container context from which it is used. In container-managed persistence, a primary key can be a serializable object defined specifically for the bean by the bean developer, or its definition can be deferred until deployment. The primary key defines attributes that can be used to locate a specific bean in the database. In this case we need only one attribute, id, but it is possible for a primary key to have several attributes, all of which uniquely identify a bean's data. We will examine primary keys in detail in Chapter 11; for now, we'll specify that the Ship EJB use a simple single-value primary key of type java.lang.Integer.

9.3.4 The ShipBean Class

No bean is complete without its implementation class. Now that we have defined the Ship EJB's remote interfaces and primary key, we are ready to define the ShipBean itself. The ShipBean will reside on the EJB server. When a client application or bean invokes a business method on the Ship EJB's remote interface, that method invocation will be received by the EJB object, which will then delegate it to the ShipBean instance.

When developing any bean, we have to use the bean's remote interfaces as a guide. Business methods defined in the remote interface must be duplicated in the bean class. In container-managed beans, the create methods of the home interface must also have matching methods in the bean class, according to the EJB 1.1 specification. Finally, callback methods defined by the javax.ejb.EntityBean interface must be implemented. Here is the code for the ShipBean class:

package com.titan.ship;

import javax.ejb.EntityContext;

public class ShipBean implements javax.ejb.EntityBean {
    public Integer id;
    public String name;
    public int capacity;
    public double tonnage;

    public EntityContext context;
        
    public Integer ejbCreate(Integer id, String name, int capacity, double tonnage) {
        this.id = id;
        this.name = name;
        this.capacity = capacity;
        this.tonnage = tonnage;
        return null;
    }

    public Integer ejbCreate(Integer id, String name) {
        this.id = id;
        this.name = name;
        capacity = 0;
        tonnage = 0;
        return null;
    }

    public void ejbPostCreate(Integer id, String name, int capacity, 
        double tonnage) {
        Integer pk = (Integer)context.getPrimaryKey();
        // Do something useful with the primary key.
    }

    public void ejbPostCreate(int id, String name) {
        ShipRemote myself = (ShipRemote)context.getEJBObject();
        // Do something useful with the EJBObject reference.
    }
    public void setEntityContext(EntityContext ctx) {
        context = ctx;
    }
    public void unsetEntityContext() { 
        context = null;
    }
    public void ejbActivate() {}
    public void ejbPassivate() {}
    public void ejbLoad() {}
    public void ejbStore() {}
    public void ejbRemove() {}

    public String getName() {
        return name;
    }
    public void setName(String n) {
        name = n;
    }
    public void setCapacity(int cap) {
        capacity = cap;
    }
    public int getCapacity() {
        return capacity;
    }
    public double getTonnage() {
        return tonnage;
    }
    public void setTonnage(double tons) {
        tonnage = tons;
    }
}

The Ship EJB defines four persistence fields: id, name, capacity, and tonnage. These fields represent the persistence state of the Ship EJB; they are the state that defines a unique Ship entity in the database. The Ship EJB also defines another field, context, which holds the bean's EntityContext. We'll have more to say about this field later.

The set and get methods are the business methods we defined for the Ship EJB; both the remote interface and the bean class must support them. This means that the signatures of these methods must be exactly the same, except for the javax.ejb.RemoteException. The bean class's business methods aren't required to throw the RemoteException. This makes sense because these methods aren't actually invoked remotely—they're invoked by the EJB object. If a communication problem occurs, the container will throw the RemoteException for the bean automatically.

9.3.5 Implementing the javax.ejb.EntityBean Interface

Because it is an entity bean, the Ship EJB must implement the javax.ejb.EntityBean interface. The EntityBean interface contains a number of callback methods that the container uses to alert the bean instance of various runtime events:

public interface javax.ejb.EntityBean extends javax.ejb.EnterpriseBean {
    public abstract void ejbActivate() throws RemoteException;
    public abstract void ejbPassivate() throws RemoteException;
    public abstract void ejbLoad() throws RemoteException;
    public abstract void ejbStore() throws RemoteException;
    public abstract void ejbRemove() throws RemoteException;
    public abstract void setEntityContext(EntityContext ctx) 
        throws RemoteException;
    public abstract void unsetEntityContext() throws RemoteException;
}

Each callback method is called at a specific time during the life cycle of a ShipBean instance. In many cases, container-managed beans (like the Ship EJB) don't need to do anything when a callback method is invoked. The persistence of container-managed beans is managed automatically; much of the resources and logic that might be managed by these methods is already handled by the container.

This version of the Ship EJB has empty implementations for its callback methods. It is important to note, however, that even a container-managed bean can take advantage of these callback methods if needed; we just don't need them in our ShipBean class at this time. The callback methods are examined in detail in Chapter 11. You should read that chapter to learn more about the callback methods and when they are invoked.

9.3.6 The Create Methods

When a create() method is invoked on the home interface, the EJB home delegates it to the bean instance in the same way that business methods on the remote interface are handled. This means we need an ejbCreate() method in the bean class that corresponds to each create() method in the home interface.

The ejbCreate() method returns a null value of type Integer for the bean's primary key. The return value of the ejbCreate() method for a container-managed bean is actually ignored by the container.

EJB 1.1 changed the ejbCreate() method's return value from void, which was the return type in EJB 1.0, to the primary key type to facilitate subclassing—this makes it easier for a bean-managed bean to extend a container-managed bean. In EJB 1.0, because the return type was void, this was not possible: Java won't allow you to overload methods with different return values. By changing this definition so that a bean-managed bean can extend a container-managed bean, the EJB 1.1 specification allows vendors to support container-managed persistence by extending the container-managed bean with a generated bean-managed bean—a fairly simple solution to a difficult problem. Bean developers can also take advantage of inheritance to change an existing CMP bean into a BMP bean, which may be necessary to overcome difficult persistence problems.

For every create() method defined in the entity bean's home interface, there must be a corresponding ejbPostCreate() method in the bean instance class. In other words, the ejbCreate() and ejbPostCreate() methods occur in pairs with matching signatures; there must be one pair for each create() method defined in the home interface.

9.3.6.1 ejbCreate( ) and ejbPostCreate( )

In a container-managed bean, the ejbCreate() method is called just prior to writing the bean's container-managed fields to the database. Values passed in to the ejbCreate() method should be used to initialize the fields of the bean instance. Once the ejbCreate() method completes, a new record, based on the container-managed fields, is written to the database.

The bean developer must ensure that the ejbCreate() method sets the persistence fields that correspond to the fields of the primary key. When a compound primary key is defined for a container-managed bean, it must define fields that match one or more of the container-managed (persistence) fields in the bean class. The fields must match exactly with regard to type and name. At runtime, the container will assume that fields in the primary key match some or all of the fields in the bean class. When a new bean is created, the container will use the container-managed fields in the bean class to instantiate and populate a primary key for the bean automatically.

Once the bean's state has been populated and its EntityContext has been established, an ejbPostCreate() method is invoked. This method gives the bean an opportunity to perform any postprocessing prior to servicing client requests. The bean identity isn't available to the bean during the call to ejbCreate(), but it is available in the ejbPostCreate() method. This means that the bean can access its own primary key and EJB object, which can be useful for initializing the bean instance prior to servicing business-method invocations. You can use the ejbPostCreate() method to perform any additional initialization. Each ejbPostCreate() method must have the same parameters as its corresponding ejbCreate() method. The ejbPostCreate() method returns void.

For more information about the ejbCreate() and ejbPostCreate() methods and how they relate to the life cycle of entity beans, see Chapter 11.

9.3.6.2 Using ejbLoad( ) and ejbStore( ) in container-managed beans

The process of ensuring that the database record and the entity bean instance are equivalent is called synchronization. In container-managed persistence, the bean's container-managed fields are automatically synchronized with the database. In most cases we will not need the ejbLoad() and ejbStore() methods, because persistence in container-managed beans is uncomplicated. These methods are covered in more detail in Chapter 11.

9.3.7 Deployment Descriptor

With a complete definition of the Ship EJB, including the remote interface and the home interface, we are ready to create a deployment descriptor. The following code shows the bean's XML deployment descriptor. The <cmp-field> element is particularly important. These elements list the fields that are managed by the container; they have the same meaning as they do in EJB 2.0 container-managed persistence:

<!DOCTYPE ejb-jar PUBLIC "-//Sun Microsystems, Inc.//DTD Enterprise
JavaBeans 1.1//EN" "http://java.sun.com/j2ee/dtds/ejb-jar_1_1.dtd">

<ejb-jar>
    <enterprise-beans>
        <entity>
            <description>
                This bean represents a cruise ship.
            </description>
            <ejb-name>ShipEJB</ejb-name>
            <home>com.titan.ship.ShipHomeRemote</home>
            <remote>com.titan.ship.ShipRemote</remote>
            <ejb-class>com.titan.ship.ShipBean</ejb-class>
            <persistence-type>Container</persistence-type>
            <prim-key-class>java.lang.Integer</prim-key-class>
            <reentrant>False</reentrant>
            <cmp-field><field-name>id</field-name></cmp-field>
            <cmp-field><field-name>name</field-name></cmp-field>
            <cmp-field><field-name>capacity</field-name></cmp-field>
            <cmp-field><field-name>tonnage</field-name></cmp-field>
            <primkey-field>id</primkey-field>
        </entity>
    </enterprise-beans>
 
    <assembly-descriptor>
        <security-role>
          <description>
           This role represents everyone who is allowed full access 
           to the Ship EJB.
          </description>
          <role-name>everyone</role-name>
        </security-role>

        <method-permission>
        <role-name>everyone</role-name>
        <method>
            <ejb-name>ShipEJB</ejb-name>
            <method-name>*</method-name>
        </method>
    </method-permission>

    <container-transaction>
        <method>
            <ejb-name>ShipEJB</ejb-name>
            <method-name>*</method-name>
        </method>
        <trans-attribute>Required</trans-attribute>
    </container-transaction>
    </assembly-descriptor>
</ejb-jar>

The <cmp-field> elements list all the container-managed fields in the entity bean class. These are the fields that will be persisted in the database and are managed by the container at runtime.

figs/book.gifPlease refer to Workbook Exercise 9.1, A CMP 1.1 Entity Bean. This workbook is available free, in PDF format, at http://www.oreilly.com/catalog/entjbeans3/workbooks.

[1]   Chapter 15 explains when you should not define any create methods in the home interface.

CONTENTS