Bean-Managed Persistence Examples

Data is at the heart of most business apps. In J2EE apps, entity beans represent the business objects that are stored in a database. For entity beans with bean-managed persistence, write the code for the database access calls. Although writing this code is an additional responsibility, you will have more control over how the entity bean accesses a database.

This chapter discusses the coding techniques for entity beans with bean-managed persistence.

 

The SavingsAccountEJB Example

The entity bean illustrated in this section represents a simple bank account. The state of SavingsAccountEJB is stored in the savingsaccount table of a relational database. The savingsaccount table is created by the following SQL statement:

CREATE TABLE savingsaccount
   (id VARCHAR(3) 
   CONSTRAINT pk_savingsaccount PRIMARY KEY,
   firstname VARCHAR(24),
   lastname  VARCHAR(24),
   balance   NUMERIC(10,2));

The SavingsAccountEJB example requires the following code:

This example also makes use of the following classes:

  • A utility class named InsufficientBalanceException
  • A client class called SavingsAccountClient

The source code for this example is in the j2eetutorial/examples/src/ejb/savingsaccount directory. To compile the code, go to the j2eetutorial/examples directory and type ant savingsaccount. A sample SavingsAccountApp.ear file is in the j2eetutorial/examples/ears directory.

 

Entity Bean Class

The sample entity bean class is called SavingsAccountBean. As you look through its code, note that it meets the requirements of any entity bean with bean-managed persistence. First of all, it implements the following:

  • EntityBean interface
  • Zero or more ejbCreate and ejbPostCreate methods
  • Finder methods
  • Business methods
  • Home methods

In addition, an entity bean class with bean-managed persistence has these requirements:

  • The class is defined as public.
  • The class cannot be defined as abstract or final.
  • It contains an empty constructor.
  • It does not implement the finalize method.

The EntityBean Interface

The EntityBean interface extends the EnterpriseBean interface, which extends the Serializable interface. The EntityBean interface declares a number of methods, such as ejbActivate and ejbLoad, which implement in your entity bean class. These methods are discussed in later sections.

The ejbCreate Method

When the client invokes a create method, the EJB container invokes the corresponding ejbCreate method. Typically, an ejbCreate method in an entity bean performs the following tasks:

  • Inserts the entity state into the database
  • Initializes the instance variables
  • Returns the primary key

The ejbCreate method of SavingsAccountBean inserts the entity state into the database by invoking the private insertRow method, which issues the SQL INSERT statement. Here is the source code for the ejbCreate method:

public String ejbCreate(String id, String firstName, 
   String lastName, BigDecimal balance)
   throws CreateException {

   if (balance.signum() == -1)  {
      throw new CreateException
         ("A negative initial balance is not allowed.");
   }

   try {
      insertRow(id, firstName, lastName, balance);
   } catch (Exception ex) {
       throw new EJBException("ejbCreate: " + 
          ex.getMessage());
   }

   this.id = id;
   this.firstName = firstName;
   this.lastName = lastName;
   this.balance = balance;

   return id;
}

Although the SavingsAccountBean class has just one ejbCreate method, an enterprise bean may contain multiple ejbCreate methods. For an example, see the CartEJB.java source code in the j2eetutorial/examples/src/ejb/cart directory.

When writing an ejbCreate method for an entity bean, be sure to follow these rules:

  • The access control modifier must be public.
  • The return type must be the primary key.
  • The arguments must be legal types for the Java RMI API.
  • The method modifier cannot be final or static.

The throws clause may include the javax.ejb.CreateException and exceptions that are specific to your app. An ejbCreate method usually throws a CreateException if an input parameter is invalid. If an ejbCreate method cannot create an entity because another entity with the same primary key already exists, it should throw a javax.ejb.DuplicateKeyException (a subclass of CreateException). If a client receives a CreateException or a DuplicateKeyException, it should assume that the entity was not created.

The state of an entity bean may be directly inserted into the database by an app that is unknown to the J2EE server. For example, a SQL script might insert a row into the savingsaccount table. Although the entity bean for this row was not created by an ejbCreate method, the bean can be located by a client program.

The ejbPostCreate Method

For each ejbCreate method, write an ejbPostCreate method in the entity bean class. The EJB container invokes ejbPostCreate immediately after it calls ejbCreate. Unlike the ejbCreate method, the ejbPostCreate method can invoke the getPrimaryKey and getEJBObject methods of the EntityContext interface.

The signature of an ejbPostCreate method must meet the following requirements:

  • The number and types of arguments must match a corresponding ejbCreate method.
  • The access control modifier must be public.
  • The method modifier cannot be final or static.
  • The return type must be void.

The throws clause may include the javax.ejb.CreateException and exceptions that are specific to your app.

The ejbRemove Method

A client deletes an entity bean by invoking the remove method. This invocation causes the EJB container to call the ejbRemove method, which deletes the entity state from the database. In the SavingsAccountBean class, the ejbRemove method invokes a private method named deleteRow, which issues a SQL DELETE statement. The ejbRemove method is short:

public void ejbRemove() {
    try {
        deleteRow(id);
    catch (Exception ex) {
        throw new EJBException("ejbRemove: " +
        ex.getMessage());
    }
}

If the ejbRemove method encounters a system problem, it should throw the javax.ejb.EJBException. If it encounters an app error, it should throw a javax.ejb.RemoveException.

An entity bean may also be removed directly by a database deletion. For example, if a SQL script deletes a row that contains an entity bean state, then that entity bean is removed.

The ejbLoad and ejbStore Methods

If the EJB container needs to synchronize the instance variables of an entity bean with the corresponding values stored in a database, it invokes the ejbLoad and ejbStore methods. The ejbLoad method refreshes the instance variables from the database, and the ejbStore method writes the variables to the database. The client may not call ejbLoad and ejbStore.

If a business method is associated with a transaction, the container invokes ejbLoad before the business method executes. Immediately after the business method executes, the container calls ejbStore. Because the container invokes ejbLoad and ejbStore, you do not have to refresh and store the instance variables in your business methods. The SavingsAccountBean class relies on the container to synchronize the instance variables with the database. Therefore, the business methods of SavingsAccountBean should be associated with transactions.

If the ejbLoad and ejbStore methods cannot locate an entity in the underlying database, they should throw the javax.ejb.NoSuchEntityException. This exception is a subclass of EJBException. Because EJBException is a subclass of RuntimeException, you do not have to include it in the throws clause. When NoSuchEntityException is thrown, the EJB container wraps it in a RemoteException before returning it to the client.

In the SavingsAccountBean class, ejbLoad invokes the loadRow method, which issues a SQL SELECT statement and assigns the retrieved data to the instance variables. The ejbStore method calls the storeRow method, which stores the instance variables in the database with a SQL UPDATE statement. Here is the code for the ejbLoad and ejbStore methods:

public void ejbLoad() {

   try {
      loadRow();
   } catch (Exception ex) {
      throw new EJBException("ejbLoad: " + 
         ex.getMessage());
   }
}

public void ejbStore() {

   try {
      storeRow();
   } catch (Exception ex) {
      throw new EJBException("ejbStore: " + 
         ex.getMessage());
   }
}

The Finder Methods

The finder methods allow clients to locate entity beans. The SavingsAccountClient program locates entity beans with three finder methods:

SavingsAccount jones = home.findByPrimaryKey("836");
...
Collection c = home.findByLastName("Smith");
...
Collection c = home.findInRange(20.00, 99.00);

For every finder method available to a client, the entity bean class must implement a corresponding method that begins with the prefix ejbFind. The SavingsAccountBean class, for example, implements the ejbFindByLastName method as follows:

public Collection ejbFindByLastName(String lastName)
   throws FinderException {

   Collection result;

   try {
      result = selectByLastName(lastName);
   } catch (Exception ex) {
      throw new EJBException("ejbFindByLastName " + 
         ex.getMessage());
   }
   return result;
}

The finder methods that are specific to your app, such as ejbFindByLastName and ejbFindInRange, are optional--but the ejbFindByPrimaryKey method is required. As its name implies, the ejbFindByPrimaryKey method accepts as an argument the primary key, which it uses to locate an entity bean. In the SavingsAccountBean class, the primary key is the id variable. Here is the code for the ejbFindByPrimaryKey method:

public String ejbFindByPrimaryKey(String primaryKey) 
   throws FinderException {

   boolean result;

   try {
      result = selectByPrimaryKey(primaryKey);
   } catch (Exception ex) {
      throw new EJBException("ejbFindByPrimaryKey: " + 
         ex.getMessage());
   }

   if (result) {
      return primaryKey;
   }
   else {
      throw new ObjectNotFoundException
         ("Row for id " + primaryKey + " not found.");
   }
}

The ejbFindByPrimaryKey method may look strange to you, because it uses a primary key for both the method argument and return value. However, remember that the client does not call ejbFindByPrimaryKey directly. It is the EJB container that calls the ejbFindByPrimaryKey method. The client invokes the findByPrimaryKey method, which is defined in the home interface.

The following list summarizes the rules for the finder methods that you implement in an entity bean class with bean-managed persistence:

  • The ejbFindByPrimaryKey method must be implemented.
  • A finder method name must start with the prefix ejbFind.
  • The access control modifier must be public.
  • The method modifier cannot be final or static.
  • The arguments and return type must be legal types for the Java RMI API. (This requirement applies only to methods defined in a remote--not local--home interface.)
  • The return type must be the primary key or a collection of primary keys.

The throws clause may include the javax.ejb.FinderException and exceptions that are specific to your app. If a finder method returns a single primary key and the requested entity does not exist, the method should throw the javax.ejb.ObjectNotFoundException (a subclass of FinderException). If a finder method returns a collection of primary keys and it does not find any objects, it should return an empty collection.

The Business Methods

The business methods contain the business logic that you want to encapsulate within the entity bean. Usually, the business methods do not access the database, allowing you to separate the business logic from the database access code. The SavingsAccountBean class contains the following business methods:

public void debit(BigDecimal amount) 
   throws InsufficientBalanceException {

   if (balance.compareTo(amount) == -1) {
       throw new InsufficientBalanceException();
   }
   balance = balance.subtract(amount);
}

public void credit(BigDecimal amount) {

   balance = balance.add(amount);
}
 
public String getFirstName() {
 
   return firstName;
}
 
public String getLastName() {
 
   return lastName;
}
  
public BigDecimal getBalance() {

   return balance;
}

The SavingsAccountClient program invokes the business methods as follows:

BigDecimal zeroAmount = new BigDecimal("0.00");
SavingsAccount duke = home.create("123", "Duke", "Earl",
    zeroAmount);
...
duke.credit(new BigDecimal("88.50"));
duke.debit(new BigDecimal("20.25"));
BigDecimal balance = duke.getBalance();

The requirements for the signature of a business method are the same for both session and entity beans:

  • The method name must not conflict with a method name defined by the EJB architecture. For example, you cannot call a business method ejbCreate or ejbActivate.
  • The access control modifier must be public.
  • The method modifier cannot be final or static.
  • The arguments and return types must be legal types for the Java RMI API. This requirement applies only to methods defined in a remote--not local--home interface.

The throws clause may include the exceptions that you define for your app. The debit method, for example, throws the InsufficientBalanceException. To indicate a system-level problem, a business method should throw the javax.ejb.EJBException.

The Home Methods

A home method contains the business logic that applies to all entity beans of a particular class. In contrast, the logic in a business method applies to a single entity bean, an instance with a unique identity. During a home method invocation, the instance has neither a unique identity nor a state that represents a business object. Consequently, a home method must not access the bean's persistence state (instance variables). (For container-managed persistence, a home method also must not access relationships.)

Typically, a home method locates a collection of bean instances and invokes business methods as it iterates through the collection. This approach is taken by the ejbHomeChargeForLowBalance method of the SavingsAccountBean class. The ejbHomeChargeForLowBalance method applies a service charge to all savings accounts with balances less than a specified amount. The method locates these accounts by invoking the findInRange method. As it iterates through the collection of SavingsAccount instances, the ejbHomeChargeForLowBalance method checks the balance and invokes the debit business method. Here is the source code of the ejbHomeChargeForLowBalance method:

public void ejbHomeChargeForLowBalance(
    BigDecimal minimumBalance, BigDecimal charge) 
    throws InsufficientBalanceException {

   try {
       SavingsAccountHome home =
       (SavingsAccountHome)context.getEJBHome();
       Collection c = home.findInRange(new BigDecimal("0.00"),
           minimumBalance.subtract(new BigDecimal("0.01")));

       Iterator i = c.iterator();

       while (i.hasNext()) {
          SavingsAccount account = (SavingsAccount)i.next();
          if (account.getBalance().compareTo(charge) == 1) {
             account.debit(charge);
          }
       }

   } catch (Exception ex) {
       throw new EJBException("ejbHomeChargeForLowBalance: " 
           + ex.getMessage());
   } 
} 

The home interface defines a corresponding method named chargeForLowBalance). Since the interface provides the client view, the SavingsAccountClient program invokes the home method as follows:

SavingsAccountHome home;
...
home.chargeForLowBalance(new BigDecimal("10.00"), 
   new BigDecimal("1.00"));

In the entity bean class, the implementation of a home method must adhere to these rules:

  • A home method name must start with the prefix ejbHome.
  • The access control modifier must be public.
  • The method modifier cannot be static.

The throws clause may include exceptions that are specific to your app; it must not throw the java.rmi.RemoteException.

Database Calls

The table below summarizes the database access calls in the SavingsAccountBean class. The business methods of the SavingsAccountBean class are absent from the preceding table because they do not access the database. Instead, these business methods update the instance variables, which are written to the database when the EJB container calls ejbStore. Another developer might have chosen to access the database in the business methods of the SavingsAccountBean class. This choice is one of those design decisions that depend on the specific needs of your app.

Before accessing a database, connect to it.

 

SQL Statements in SavingsAccountBean

Method SQL Statement
ejbCreate INSERT
ejbFindByPrimaryKey SELECT
ejbFindByLastName SELECT
ejbFindInRange SELECT
ejbLoad SELECT
ejbRemove DELETE
ejbStore UPDATE

 

Home Interface

The home interface defines the methods that allow a client to create and find an entity bean. The SavingsAccountHome interface follows:

import java.util.Collection;
import java.math.BigDecimal;
import java.rmi.RemoteException;
import javax.ejb.*;

public interface SavingsAccountHome extends EJBHome {

    public SavingsAccount create(String id, String firstName, 
        String lastName, BigDecimal balance)
        throws RemoteException, CreateException;
    
    public SavingsAccount findByPrimaryKey(String id) 
        throws FinderException, RemoteException;
    
    public Collection findByLastName(String lastName)
        throws FinderException, RemoteException;

    public Collection findInRange(BigDecimal low, 
        BigDecimal high)
        throws FinderException, RemoteException;

    public void chargeForLowBalance(BigDecimal minimumBalance, 
       BigDecimal charge)
       throws InsufficientBalanceException, RemoteException;
}

create Method Definitions

Each create method in the home interface must conform to the following requirements:

  • It has the same number and types of arguments as its matching ejbCreate method in the enterprise bean class.
  • It returns the remote interface type of the enterprise bean.
  • The throws clause includes the exceptions specified by the throws clause of the corresponding ejbCreate and ejbPostCreate methods.
  • The throws clause includes the javax.ejb.CreateException.
  • If the method is defined in a remote--not local--home interface, then the throws clause includes the java.rmi.RemoteException.

Finder Method Definitions

Every finder method in the home interface corresponds to a finder method in the entity bean class. The name of a finder method in the home interface begins with find, whereas the corresponding name in the entity bean class begins with ejbFind. For example, the SavingsAccountHome class defines the findByLastName method, and the SavingsAccountBean class implements the ejbFindByLastName method. The rules for defining the signatures of the finder methods of a home interface follow.

  • The number and types of arguments must match those of the corresponding method in the entity bean class.
  • The return type is the entity bean's remote interface type, or a collection of those types.
  • The exceptions in the throws clause include those of the corresponding method in the entity bean class.
  • The throws clause contains the javax.ejb.FinderException.
  • If the method is defined in a remote--not local--home interface, then the throws clause includes the java.rmi.RemoteException.

Home Method Definitions

Each home method definition in the home interface corresponds to a method in the entity bean class. In the home interface, the method name is arbitrary, provided that it does not begin with create or find. In the bean class, the matching method name begins with ejbHome. For example, in the SavingsAccountBean class the name is ejbHomeChargeForLowBalance, but in the SavingsAccountHome interface the name is chargeForLowBalance.

The home method signature must follow the same rules specified for finder methods in the previous section (except that a home method does not throw a FinderException).

 

Remote Interface

The remote interface extends javax.ejb.EJBObject and defines the business methods that a remote client may invoke. Here is the SavingsAccount remote interface:

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

public interface SavingsAccount extends EJBObject {
    
    public void debit(BigDecimal amount)
        throws InsufficientBalanceException, RemoteException;

    public void credit(BigDecimal amount)
        throws RemoteException;
 
    public String getFirstName()
        throws RemoteException;

    public String getLastName()
        throws RemoteException;
   
    public BigDecimal getBalance()
        throws RemoteException;
}

The requirements for the method definitions in a remote interface are the same for both session and entity beans:

  • Each method in the remote interface must match a method in the enterprise bean class.
  • The signatures of the methods in the remote interface must be identical to the signatures of the corresponding methods in the enterprise bean class.
  • The arguments and return values must be valid RMI types.
  • The throws clause must include java.rmi.RemoteException.

A local interface has the same requirements, with the following exceptions:

  • The arguments and return values are not required to be valid RMI types.
  • The throws clause does not include java.rmi.RemoteException.

 

Running the SavingsAccountEJB Example

Setting Up the Database

The instructions that follow explain how to use the SavingsAccountEJB example with a Cloudscape database. The Cloudscape software is included with the J2EE SDK download bundle.

  1. From the command-line prompt, run the Cloudscape database server by typing cloudscape -start. (When you are ready to shut down the server, type cloudscape -stop.)
  2. Create the savingsaccount database table.
    1. Go to the j2eetutorial/examples directory
    2. Type ant create-savingsaccount-table.

You may also run this example with databases other than Cloudscape. If you are using one of these other databases, you may run the j2eetutorial/examples/sql/savingsaccount.sql script to create the savingsaccount table.

Deploying the Application

  1. In deploytool, open the j2eetutorial/examples/ears/SavingsAccountApp.ear file (FileOpen).
  2. Deploy the SavingsAccountApp app (ToolsDeploy). In the Introduction dialog box, make sure that you select the Return Client JAR checkbox.

Running the Client

  1. In a terminal window, go to the j2eetutorial/examples/ears directory.
  2. Set the APPCPATH environment variable to SavingsAccountAppClient.jar.
  3. Type the following command on a single line:
       runclient -client SavingsAccountApp.ear -name
          SavingsAccountClient -textauth
    
    
  4. At the login prompts, enter guest for the user name and guest123 for the password.
  5. The client should display the following lines:
       balance = 68.25
       balance = 32.55
       456: 44.77
       730: 19.54
       268: 100.07
       836: 32.55
       456: 44.77
       4.00
       7.00
    
    

 

deploytool Tips for Entity Beans with Bean-Managed Persistence

To build an entity bean:

  1. In the New Enterprise Bean wizard, specify the bean's type and persistent management.
    1. In the General dialog box, select the Entity radio button.
    2. In the Entity Settings dialog box, select the radio button for Bean-Managed Persistence.
  2. In the Resource Refs tab, specify the resource factories referenced by the bean. These settings enable the bean to connect to the database. For instructions,
  3. Before you deploy the bean, verify that the JNDI names are correct.
    1. Select the app from the tree.
    2. Select the JNDI Names tab.

 

Mapping Table Relationships for Bean-Managed Persistence

In a relational database, tables can be related by common columns. The relationships between the tables affect the design of their corresponding entity beans. The entity beans discussed in this section are backed up by tables with the following types of relationships:

  • One-to-one
  • One-to-many
  • Many-to-many

 

One-to-One Relationships

In a one-to-one relationship, each row in a table is related to a single row in another table. For example, in a warehouse app, a storagebin table might have a one-to-one relationship with a widget table. This app would model a physical warehouse in which each storage bin contains one type of widget and each widget resides in one storage bin.

Figure 5-1 illustrates the storagebin and widget tables. Because the storagebinid uniquely identifies a row in the storagebin table, it is that table's primary key. The widgetid is the primary key of the widget table. The two tables are related because the widgetid is also a column in the storagebin table. By referring to the primary key of the widget table, the widgetid in the storagebin table identifies which widget resides in a particular storage bin in the warehouse. Because the widgetid of the storagebin table refers to the primary key of another table, it is called a foreign key. (The figures in this chapter denote a primary key with PK and a foreign key with FK.)

Figure 5-1 One-to-One Table Relationship

A dependent (child) table includes a foreign key that matches the primary key of the referenced (parent) table. The values of the foreign keys in the storagebin (child) table depend on the primary keys in the widget (parent) table. For example, if the storagebin table has a row with a widgetid of 344, then the widget table should also have a row whose widgetid is 344.

When designing a database app, you may choose to enforce the dependency between the parent and child tables. There are two ways to enforce such a dependency: by defining a referential constraint in the database or by performing checks in the app code. The storagebin table has a referential constraint named fk_widgetid:

CREATE TABLE storagebin
   (storagebinid VARCHAR(3) 
    CONSTRAINT pk_storagebin PRIMARY KEY,
    widgetid VARCHAR(3),
    quantity INTEGER,
    CONSTRAINT fk_widgetid
    FOREIGN KEY (widgetid)
     REFERENCES widget(widgetid));

The source code for the following example is in the j2eetutorial/examples/src/ejb/storagebin directory. To compile the code, go to the j2eetutorial/examples directory and type ant storagebin. A sample StorageBinApp.ear file is in the j2eetutorial/examples/ears directory.

The StorageBinBean and WidgetBean classes illustrate the one-to-one relationship of the storagebin and widget tables. The StorageBinBean class contains variables for each column in the storagebin table, including the foreign key, widgetId:

private String storageBinId;
private String widgetId;
private int quantity;

The ejbFindByWidgetId method of the StorageBinBean class returns the storageBinId that matches a given widgetId:

public String ejbFindByWidgetId(String widgetId)
   throws FinderException {

   String storageBinId;

   try {
      storageBinId = selectByWidgetId(widgetId);
    } catch (Exception ex) {
        throw new EJBException("ejbFindByWidgetId: " + 
           ex.getMessage());
    }

   if (storageBinId == null) {
      throw new ObjectNotFoundException
         ("Row for widgetId " + widgetId + " not found.");
   }
   else {
      return storageBinId;
   }
}

The ejbFindByWidgetId method locates the widgetId by querying the database in the selectByWidgetId method:

private String selectByWidgetId(String widgetId) 
   throws SQLException {

   String storageBinId;

   String selectStatement =
         "select storagebinid " +
         "from storagebin where widgetid = ? ";
   PreparedStatement prepStmt =
         con.prepareStatement(selectStatement);
   prepStmt.setString(1, widgetId);

   ResultSet rs = prepStmt.executeQuery();

   if (rs.next()) {
      storageBinId = rs.getString(1);
   }
   else {
      storageBinId = null;
   }

   prepStmt.close();
   return storageBinId;
}

To find out in which storage bin a widget resides, the StorageBinClient program calls the findByWidgetId method:

String widgetId = "777";
StorageBin storageBin = 
   storageBinHome.findByWidgetId(widgetId);
String storageBinId = (String)storageBin.getPrimaryKey();
int quantity = storageBin.getQuantity();

Running the StorageBinEJB Example

  1. Create the storagebin database table.
    1. Go to the j2eetutorial/examples directory.
    2. Type ant create-storagebin-table.
  2. Deploy the StorageBinApp.ear file (located in the j2eetutorial/examples/ears directory).
  3. Run the client.
    1. Go to the j2eetutorial/examples/ears directory.
    2. Set the APPCPATH environment variable to StorageBinAppClient.jar.
    3. Type the following command on a single line:
            runclient -client StorageBinApp.ear -name 
               StorageBinClient    -textauth
      
      
    4. At the login prompts, enter guest for the user name and guest123 for the password.

 

One-to-Many Relationships

If the primary key in a parent table matches multiple foreign keys in a child table, then the relationship is one-to-many. This relationship is common in database apps. For example, an app for a sports league might access a team table and a player table. Each team has multiple players, and each player belongs to a single team. Every row in the child table (player) has a foreign key identifying the player's team. This foreign key matches the team table's primary key.

The sections that follow describe how you might implement one-to-many relationships in entity beans. When designing such entity beans, decide whether both tables are represented by entity beans, or just one.

A Helper Class for the Child Table

Not every database table needs to be mapped to an entity bean. If a database table doesn't represent a business entity, or if it stores information that is contained in another entity, then the table should be represented with a helper class. In an online shopping app, for example, each order submitted by a customer can have multiple line items. The app stores the information in the database tables shown by Figure 5-2.

Figure 5-2 One-to-Many Relationship: Order and Line Items

Not only does a line item belong to an order, it also does not exist without the order. Therefore, the lineitems table should be represented with a helper class and not with an entity bean. Using a helper class in this case is not required, but doing so might improve performance because a helper class uses fewer system resources than an entity bean.

The source code for the following example is in the j2eetutorial/examples/src/ejb/order directory. To compile the code, go to the j2eetutorial/examples directory and type ant order. A sample OrderApp.ear file is in the j2eetutorial/examples/ears directory.

The LineItem and OrderBean classes show how to implement a one-to-many relationship with a helper class (LineItem). The instance variables in the LineItem class correspond to the columns in the lineitems table. The itemNo variable matches the primary key for the lineitems table, and the orderId variable represents the table's foreign key. Here is the source code for the LineItem class:

public class LineItem implements java.io.Serializable {

   String productId;
   int quantity;
   double unitPrice;
   int itemNo;
   String orderId;
   

   public LineItem(String productId, int quantity,
     double unitPrice, int itemNo, String orderId) {

      this.productId = productId;
      this.quantity = quantity;
      this.unitPrice = unitPrice;
      this.itemNo = itemNo;
      this.orderId = orderId;
   }

   public String getProductId() {
      return productId;
   }

   public int getQuantity() {
      return quantity;
   }

   public double getUnitPrice() {
      return unitPrice;
   }

   public int getItemNo() {
      return itemNo;
   }

   public String getOrderId() {
      return orderId;
   }
}

The OrderBean class contains an ArrayList variable named lineItems. Each element in the lineItems variable is a LineItem object. The lineItems variable is passed to the OrderBean class in the ejbCreate method. For every LineItem object in the lineItems variable, the ejbCreate method inserts a row into the lineitems table. It also inserts a single row into the orders table. The code for the ejbCreate method follows:

public String ejbCreate(String orderId, String customerId,
    String status, double totalPrice, ArrayList lineItems)
    throws CreateException {

    try {
       insertOrder(orderId, customerId, status, totalPrice);
       for (int i = 0; i < lineItems.size(); i++) {
          LineItem item = (LineItem)lineItems.get(i);
          insertItem(item);
       }
    } catch (Exception ex) {
        throw new EJBException("ejbCreate: " + 
           ex.getMessage());
    }

    this.orderId = orderId;
    this.customerId = customerId;
    this.status = status;
    this.totalPrice = totalPrice;
    this.lineItems = lineItems ;

    return orderId;
}

The OrderClient program creates and loads an ArrayList of LineItem objects. The program passes this ArrayList to the entity bean when it invokes the create method:

ArrayList lineItems = new ArrayList();
lineItems.add(new LineItem("p23", 13, 12.00, 1, "123"));
lineItems.add(new LineItem("p67", 47, 89.00, 2, "123"));
lineItems.add(new LineItem("p11", 28, 41.00, 3, "123"));
...
Order duke = home.create("123", "c44", "open",
   totalItems(lineItems), lineItems);

Other methods in the OrderBean class also access both database tables. The ejbRemove method, for example, not only deletes a row from the orders table, but also deletes all corresponding rows in the lineitems table. The ejbLoad and ejbStore methods synchronize the state of an OrderEJB instance, including the lineItems ArrayList, with the orders and lineitems tables.

The ejbFindByProductId method enables clients to locate all orders that have a particular product. This method queries the lineitems table for all rows with a specific productId. The method returns a Collection of Order objects. The OrderClient program iterates through the Collection and prints the primary key of each order:

Collection c = home.findByProductId("p67");
Iterator i=c.iterator();
while (i.hasNext()) {
   Order order = (Order)i.next();
   String id = (String)order.getPrimaryKey();
   System.out.println(id);
}

Running the OrderEJB Example

  1. Create the orders database table:.
    1. Go to the j2eetutorial/examples directory.
    2. Type ant create-order-table.
  2. Deploy the OrderApp.ear file (located in the j2eetutorial/examples/ears directory).
  3. Run the client.
    1. Go to the j2eetutorial/examples/ears directory.
    2. Set the APPCPATH environment variable to OrderAppClient.jar.
    3. Type the following command on a single line:
            runclient -client OrderApp.ear -name OrderClient 
            -textauth
      
      
    4. At the login prompts, enter guest for the user name and guest123 for the password.

An Entity Bean for the Child Table

You should consider building an entity bean for a child table under the following conditions:

  • The information in the child table is not dependent on the parent table.
  • The business entity of the child table could exist without that of the parent table.
  • The child table might be accessed by another app that does not access the parent table.