The SavingsAccountBean Example

The entity bean illustrated in this section represents a simple bank account. The state of SavingsAccountBean 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 SavingsAccountBean example requires the following code:

This example also uses the following classes:

The source code for this example is in this directory:

<INSTALL>/j2eetutorial14/ejb/savingsaccount/src/  

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 that uses bean-managed persistence. First, it implements the following:

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

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 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:

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 only one ejbCreate method, an enterprise bean can contain multiple ejbCreate methods. For an example, see the CartBean.java source code in this directory:

<INSTALL>/j2eetutorial14/examples/ejb/cart/src/ 

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

The throws clause can include the javax.ejb.CreateException and exceptions that are specific to your application. 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 can be directly inserted into the database by an application that is unknown to the Application Server. For example, an 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, 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 Enterprise Bean's Object Reference. Often, your ejbPostCreate methods will be empty.

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

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

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 an 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 application error, it should throw a javax.ejb.RemoveException. For a comparison of system and application exceptions, see the section deploytool Tips for Entity Beans with Bean-Managed Persistence.

An entity bean can also be removed directly by a database deletion. For example, if an 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 cannot 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 an 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 using an 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 using 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 application, 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 the 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 can include the javax.ejb.FinderException and exceptions that are specific to your application. 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, and this allows 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 session beans and entity beans:

The throws clause can include 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.

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 that have 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 (see Home Method Definitions). Because 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:

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

Database Calls

Table 26-1 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 application.

Before accessing a database, you must connect to it. For more information, see Chapter 31.

Table 26-1 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 create, finder, and home methods. 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:

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.

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 SavingsAccount
Home
interface the name is chargeForLowBalance.

The home method signature must follow the same rules specified for finder methods in the preceding 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 can 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 session beans and entity beans:

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

Running the SavingsAccountBean Example

Before you run this example, you must define the data source, create the database, and deploy the SavingsAccountApp.ear file.

Defining the Data Source

Follow the instructions in Creating a Data Source. This data source is a factory for database connections. For more information, see DataSource Objects and Connection Pools.

Creating the Database Table

The instructions that follow explain how to use the SavingsAccountBean example with Derby, the database software that is included in the Application Server bundle.


Note: Application Server 8.2 includes a copy of the open source Derby database server. Application Server 8.0/8.1 includes the PointBase database server. If you are using Application Server 8.0/8.1, either follow the instructions in the J2EE Tutorial at http://java.sun.com/j2ee/1.4/docs/tutorial-update6/doc/index.html that works with Application Server 8.0/8.1 or upgrade to Application Server 8.2 (see http://java.sun.com/j2ee/1.4/download.html#appserv to download).


Create the savingsaccount database table by running the create.sql script:

  1. In a terminal window, go to this directory:
  2. <INSTALL>/j2eetutorial14/examples/ejb/savingsaccount/

  3. Type the following command, which runs the create.sql script:
  4. asant create-db_common

Deploying the Application

  1. In deploytool, open the SavingsAccountApp.ear file, which resides in this directory:
  2. <INSTALL>/j2eetutorial14/examples/ejb/provided-ears/

  3. Deploy the SavingsAccountApp application.
  4. In the Deploy Module dialog box, do the following:
    1. Select the Return Client JAR checkbox.
    2. In the field below the check box, enter the following:
    3.   <INSTALL>/j2eetutorial14/examples/ejb/savingsaccount

For detailed instructions, see Deploying the J2EE Application.

Running the Client

To run the SavingsAccountClient program, do the following:

  1. In a terminal window, go to this directory:
  2. <INSTALL>/j2eetutorial14/examples/ejb/savingsaccount/

  3. Type the following command on a single line:
  4. appclient -client SavingsAccountAppClient.jar

  5. The client should display the following lines:
  6. 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

To modify this example, see the instructions in Modifying the J2EE Application.