Prev | Next

TOC | Index

J2EETM Developer's Guide
Entity Beans


A Bean-Managed Persistence Example

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

CREATE TABLE account
   (id        VARCHAR(3) CONSTRAINT pk_account PRIMARY KEY,
    firstname VARCHAR(24),
    lastname  VARCHAR(24),
    balance   DECIMAL(10,2));
To write an entity bean, you must provide the following code:

This example also makes use of the following classes:

The source code for all of these classes are in the doc/guides/ejb/examples/account directory.

Entity Bean Class

The sample entity bean class is called AccountEJB. As you look through its code, note that it meets the requirements of every entity bean:

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 you must implement in your entity bean class. These methods are discussed 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:

The ejbCreate method of AccountEJB 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 in the AccountEJB class:

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

       if (balance < 0.00) {
          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 AccountEJB class has just one ejbCreate method, an enterprise bean may contain multiple ejbCreate methods. For an example, see the CartEJB.java source code.

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

The throws clause may include the javax.ejb.CreateException and other exceptions that are specific to your application. An ejbCreate method usually throws a CreateException if an input parameter is invalid. If an ejbCreate method cannnot 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 a non-J2EE application. For example, a SQL script might insert a row into the ACCOUNT 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, you must 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. (For more information on the getEJBObject method, see the section, Passing an Entity Bean's Object Reference on page 71.) Often, your ejbPostCreate methods will be empty.

The signature of an ejbPostCreate must meet the following requirements:

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

The ejbRemove Method

A client removes an entity bean by invoking the remove method. This invocation causes the EJB client to call the ejbRemove method, which deletes the entity state from the database. The code for the ejbRemove method in the AccountEJB class follows:

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 application error, it should throw a javax.ejb.RemoveException. (For a comparison of system and application exceptions, see the section Handling Exceptions on page 64.)

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 Method 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 container performs these functions for you. The AccountEJB class relies on the container to synchronize the instance variables with the database. Therefore, the business methods of AccountEJB should be associated with transactions. (For instructions on setting transaction attributes for methods, see the section, Running the New Enterprise Bean Wizard on page 57.)

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 AccountEJB 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 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("ejbLoad: " +
              ex.getMessage());
       }
   }

The Finder Methods

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

Account 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 AccountEJB entity bean 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());
    }

   if (result.isEmpty()) {
      throw new ObjectNotFoundException("No rows found.");
   }
   else {
      return result;
   }
}
The finder methods specific to your application, such as ejbFindByLastName and ejbFindInRange, are optional, but the ejbFindByPrimaryKey method is required. As its name infers, the ejbFindByPrimaryKey method accepts as an argument the primary key, which it uses to locate an entity bean. In the AccountEJB 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 primaryKey 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 throws clause may include the javax.ejb.FinderException, and other exceptions that are specific to your application. If a finder method returns a single primary key, it should throw the javax.ejb.ObjectNotFoundException if the requested entity does not exist. The ObjectNotFoundException is a subclass of FinderException. If a finder method returns a collection of primary keys, it should throw a FinderException.

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 business logic from the database access code. The AccountEJB entity bean contains these business methods:

public void debit(double amount)
       throws InsufficientBalanceException {

       if (balance - amount < 0) {
           throw new InsufficientBalanceException();
       }
       balance -= amount;
    }

    public void credit(double amount) {

       balance += amount;
    }

    public String getFirstName() {

       return firstName;
    }

    public String getLastName() {

       return lastName;
    }

    public double getBalance() {

       return balance;
    }
The AccountClient program invokes the business methods as follows:

Account duke = home.create("123", "Duke", "Earl");
duke.credit(88.50);
duke.debit(20.25);
double balance = duke.getBalance();
The requirements for the signature of a business method are the same for both session and entity beans:

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

Database Calls

The following table summarizes the database access calls in the AccountEJB class:

TABLE 4-1 SQL Statement in AccountEJB

Method

Resulting SQL Statement

ejbCreate insert
ejbFindByPrimaryKey select
ejbFindByLastName select
ejbFindInRange select
ejbLoad select
ejbRemove delete
ejbStore update

The business methods of the AccountEJB 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 may have chosen to access the database in the business methods of the AccountEJB class. It's a design decision that depends on the specific needs of your application.

Before accessing a database you must connect to it. See the Database Connections chapter for instructions.

Home Interface

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

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

public interface AccountHome extends EJBHome {

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

    public Collection findInRange(double low, double high)
        throws FinderException, RemoteException;
}
The create methods in the home interface must conform to these requirements:

The throws clause contains the java.rmi.RemoteException and the javax.ejb.CreateException.

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 name of one in the entity bean class begins with ejbFind. For example, the AccountHome class defines the findByLastName method, and the AccountEJB class implements the ejbFindByLastName method. The rules for defining the signatures of the finder methods of a home interface follow:

Remote Interface

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

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

public interface Account extends EJBObject {
    
    public void debit(double amount)
        throws InsufficientBalanceException, RemoteException;

    public void credit(double amount)
        throws RemoteException;

    public String getFirstName()
        throws RemoteException;

    public String getLastName()
        throws RemoteException;

    public double getBalance()
        throws RemoteException;
}
The requirements for the method definitions in a remote interface are the same for both session and entity beans:

Tips on Running the AccountEJB Example

Setting Up the Database

The instructions that follow explain how to use the AccountEJB example with a Cloudscape database. The Cloudscape software is included with the J2EE SDK download bundle. You may also run this example with databases provided by other vendors.

1. From the command-line prompt, run the Cloudscape database server:

cloudscape -start
(For more information, see the section, Cloudscape Server on page 162.)

2. Edit the script that creates the account database table.

UNIX:

cd $J2EE_HOME/doc/guides/ejb/examples/util
In the cloudTable.sh script, change <installation-location> to the directory in which you installed the J2EE SDK.

Windows:

cd %J2EE_HOME%\doc\guides\ejb\examples\util
In the cloudTable script, change <installation-location> to the directory in which you installed the J2EE SDK.

3. Run the script that creates the account database table.

UNIX:

cd $J2EE_HOME/doc/guides/ejb/examples/account
../util/cloudTable.sh
Windows:

cd %J2EE_HOME%\doc\guides\ejb\examples\account
..\util\cloudTable
Note: If you are not using a Cloudscape database, you may run the account/createTable.sql script to create the account table.

Running the New Enterprise Bean Wizard

The material in this section highlights the wizard steps that are unique to an entity bean with bean-managed persistence, such as AccountEJB. (For an introduction to the wizard, see the Getting Started chapter.)

General Dialog Box:

a. Select the Entity radio button.
b. In the Display Name field, enter AccountBean.
Entity Settings Dialog Box:

Select the radio button labelled "Bean managed persistence."
Resource References Dialog Box:

a. Click Add.
b. In the Coded Name field, enter jdbc/AccountDB.
c. In the Type column, select javax.sql.DataSource.
d. In the Authentication column, select Container.
Transaction Management Dialog Box:

For the business methods, in the Transaction Type column select Required. (The business methods are debit, credit, getFirstName, getLastName, and getBalance.)

Deploying the J2EE Application

1. Click the radio button labelled "Return Client Jar."

2. In the second dialog box, for the AccountBean entry in the Component/Reference Name field, enter MyAccount in the JNDI Name field.

3. For the jdbc/AccountDB entry, enter jdbc/Cloudscape in the JNDI Name field.



Prev | Next

TOC | Index


Copyright © 2000 Sun Microsystems, Inc. All rights reserved.