6.2. Customer Entity Bean

Entity beans can be more difficult to understand than session beans because the EJB container does more work behind the scenes. Therefore, as we present our example Customer EJB, we'll show you the entity bean code and explain the services that the EJB container provides.

The Big Picture

Our entity bean example consists of a Customer EJB with Bean-Managed Persistence (BMP). It provides persistence for the CustomerVO data we used in the previous chapter (see Listing 5.1 on page 143). With this capability, our J2EE shopping application can now identify customers from a database (and verify a customer's password), or create a new customer. We thus provide a true “login” process. The CustomerVO object includes a customer's name, password, and e-mail address. Our persistent customer will have these fields, as well as a CustomerID primary key and a boolean that indicates whether or not this customer has orders pending in our “virtual” shopping system. Figure 6-1 shows the database table for our Customer EJB and the structure of class CustomerModel that holds the persistent data inside the entity bean. At this time, our Customer EJB has no database “relationships” with any other persistent data.

Figure 6-1. Customers Database Table and CustomerModel Persistent Data


Figure 6-2 shows the architectural overview of these components. Our application's customer identity verification and new customer creation services are provided by both a Customer EJB entity bean and a CustomerSession EJB stateless session bean. The CustomerSession EJB provides the business logic and the Customer EJB provides the data persistence. The diagram also includes components introduced in previous chapters. These components are unchanged, but we want to show how everything fits together, as well as emphasize their reusability! Note that CustomerSession EJB communicates with Customer EJB through local interfaces. The Customer EJB entity bean uses CustomerDAO (DAO pattern) to implement its persistence.

Figure 6-2. Architectural Overview of the Music Shopping Cart Enterprise Application with Customer Login and Sign Up Capabilities


Design Guideline

Because Customer EJB uses bean-managed persistence, it makes sense to implement vendor independent database access. Hence, we use a DAO to implement JDBC calls to the database. Refer to “Data Access Object Pattern” on page 119 in Chapter 4 for more information.


Structure of Entity Beans

Entity bean structure is similar to session bean structure. The client accesses the bean initially through the home (or local home) interface, using either one of the finder methods or a create() call. Either of these returns a remote or local object (or a collection of objects). The remote or local interface object provides the data access methods (which are business methods from an entity bean's point of view). As with the session bean, the EJB container intercepts these calls and forwards them to the bean implementation class.

Figure 6-3 shows the classes and interfaces of our Customer entity bean with local interfaces. (To avoid cluttering the diagram, we show the local and local home interfaces only.) The local home interface holds the create() methods, the finder methods, and any home methods. The local interface contains the business methods. The bean implementation class implements the EntityBean interface and contains the code for the EJB methods and the business methods.

Figure 6-3. Class Diagram Showing the EJB Classes for an Entity Bean with Local Interfaces


For Customer EJB, we implement both local and remote interfaces. As you will see later, the remote interface implementation helps us understand the system with an application test client. We use the local and local home interfaces when accessing the entity bean with CustomerSession EJB.

Design Guideline

Implementing both the remote and local interfaces of BMP entity beans is a good idea. The remote interface lets you rigorously check the code with an application test client. You can then access the entity bean in a production environment with a session bean using the entity bean's local interfaces.


Home and Local Home Interfaces

The home interface contains create methods, finder methods, and home methods. All finder methods begin with “findBy,” create methods must be called create(), and the home methods have no naming restrictions in the home or local home interface.

Listing 6.1 shows the source for the home interface, CustomerHome.java. Note that interface CustomerHome extends EJBHome and provides remote access to its methods. We therefore include RemoteException in all of the methods' throw clauses. Furthermore, method create() must specify CreateException, and all of the finder methods must specify FinderException.

Listing 6.1. CustomerHome.java
// CustomerHome.java
import java.util.Collection;
import javax.ejb.*;
import java.rmi.RemoteException;

public interface CustomerHome extends EJBHome {

  public Customer create(String name, String password,
    String email) throws RemoteException, CreateException;

  // Finder methods
  public Customer findByPrimaryKey(String customerID)
    throws RemoteException, FinderException;

  public Collection findByCustomerName(String name)
    throws RemoteException, FinderException;
  public Collection findAll()
    throws RemoteException, FinderException;

  // Home methods
  // Return the number of Customers in the database
  public int getTotalCustomers()
    throws RemoteException;
}

A finder method may return either a single object or a collection (an object implementing abstract interface Collection), depending on whether the finder method anticipates that more than one database record will satisfy the corresponding database query. For example, findByPrimaryKey() always returns a single object. A finder method that returns a single object throws an ObjectNotFoundException (a subclass of FinderException) when the database query fails to find the requested record. A finder method that returns a collection simply returns an empty collection if it does not find any objects (and no exception is thrown). Note that findByCustomerName() and findAll() both return collections.

Method getTotalCustomers() is a home method, which we access through the home interface, as follows.

// Get a home interface object
CustomerHome home =
  (CustomerHome)PortableRemoteObject.narrow(objref,
                CustomerHome.class);
// Access home method
int count = home.getTotalCustomers();

Home methods typically perform database-wide operations. They never invoke business methods that are dependent on a specific entity bean instance.

Why doesn't the create() method include either the boolean variable for the ordersPending field or the primary key field in its signature? The answer is that when we create a customer, it doesn't yet have an order associated with it. Since the ordersPending boolean is always false at creation time, the bean implementation code can initialize this boolean to false. Furthermore, the bean implementation code generates the primary key, so we exclude it from create()'s signature as well.

Design Guideline

We have chosen the Customer EJB entity bean to show you BMP because a Customer can exist without an order. This means that this version of Customer EJB is not dependent on other entity beans. In the next chapter, we implement its relationship with Order EJBs (and LineItem EJBs). Until then, the Customer EJB adds a nontrivial but isolated customer “login and sign up” capability to our system using BMP.


Listing 6.2 shows the source for the local home interface, CustomerLocalHome.java. You'll note that its methods are the same as the home interface, but it extends EJBLocalHome and we've removed all RemoteExceptions from the throw clauses. Also, findByPrimaryKey() and create() return objects implementing the CustomerLocal interface instead of the remote interface, Customer. Finders findByCustomerName() and findAll() return collections of CustomerLocal objects.

Listing 6.2. CustomerLocalHome.java
// CustomerLocalHome.java
import java.util.Collection;
import javax.ejb.*;

public interface CustomerLocalHome extends EJBLocalHome {

  public CustomerLocal create(String name,
    String password, String email) throws CreateException;

  // Finder methods
  public CustomerLocal findByPrimaryKey(String customerID)
    throws FinderException;

  public Collection findByCustomerName(String name)
    throws FinderException;

  public Collection findAll() throws FinderException;

  // Home method
  public int getTotalCustomers();
}

Remote and Local Interfaces

The remote and local interfaces specify the business methods for the entity bean. With session beans, these business methods carry out a business process. With entity beans, however, business methods typically get or update one or more fields in a database record associated with the entity bean. The business logic ensures that the new data values make sense. More typically, however, the entity bean simply accepts or retrieves data. It leaves any data consistency checking to a separate business component, such as a “business-enabled” session bean. This allows the business logic to reside in the controlling session bean.

Design Guideline

Using a business-enabled session bean is the tactic we take in our virtual shopping application. The CustomerSession EJB provides the consistency checking for new data values (so that, for example, if you change your password, it cannot be the empty string). Then, if the business rules change (say, passwords must be a certain length or they must contain at least one nonalphabetic character), the code in the CustomerSession EJB changes, not the underlying entity bean code. Furthermore, the business-enabled session bean will oftentimes coordinate the behavior of more than one related entity bean. This is yet another reason to place business logic in a controlling session bean.


In the Customer EJB, the remote interface contains methods for setting and getting the entity bean's persistent variables; i.e., those variables that are stored in the database. Note that there is no setter for the primary key (customerID), since primary keys never change. All methods specify RemoteException in their throws clauses. Like session beans, the entity bean's remote interface extends EJBObject. Listing 6.3 contains the source for Customer.java.

Listing 6.3. Customer.java
// Customer.java
import javax.ejb.EJBObject;
import java.rmi.RemoteException;

public interface Customer extends EJBObject {

  public String getCustomerID()
    throws RemoteException;
  public String getName()
    throws RemoteException;
  public void setName(String newName)
    throws RemoteException;

  public String getPassword()
    throws RemoteException;
  public void setPassword(String newPassword)
    throws RemoteException;

  public String getEmail()
    throws RemoteException;
  public void setEmail(String newEmail)
    throws RemoteException;

  public boolean getOrdersPending()
    throws RemoteException;
  public void setOrdersPending(boolean pending)
    throws RemoteException;
}

We now show you Customer EJB's local interface, CustomerLocal. It contains the same methods as the remote interface without RemoteException in the throw clauses. And, CustomerLocal extends EJBLocalObject. Listing 6.4 contains the source for CustomerLocal.java.

Listing 6.4. CustomerLocal.java
// CustomerLocal.java
import javax.ejb.EJBLocalObject;

public interface CustomerLocal extends EJBLocalObject {

  public String getCustomerID();
  public String getName();

  public String getPassword();
  public void setPassword(String newPassword);

  public String getEmail();
  public void setEmail(String newEmail);
  public boolean getOrdersPending();
  public void setOrdersPending(boolean pending);
}

The Role of the EJB Container

Before we delve into the bean implementation code, let's explore what the EJB container does behind the scenes. First we'll show you how to create an object that implements the home interface. Once we have the home interface, we invoke either a create() method or a finder method to instantiate objects that implement the remote interface. With the remote interface object, we can call the business methods.

Optimizing Stores

All business methods, even if they only perform a data lookup, must execute within a transaction in order for the EJB container to ensure data integrity between the in-memory entity bean and the database. A key concept in entity beans is that the container wraps each transactional business method invocation with calls to ejbLoad() and ejbStore(). This guarantees that the in-memory persistent variables are always synchronized with the database.

ejbLoad();            // read data from database
business_method();    // possibly modify data
ejbStore();           // update data in database

The ejbLoad() method reads the database and refreshes the entity bean's persistent variables. This step is crucial. Because the EJB container may instantiate more than one entity bean per database record, we must refresh the persistent variables before each business method in case another client changes their values. After business_method() completes, ejbStore() writes the persistent variables back to the database. Calls to ejbStore() work well with transactional business methods that modify the persistent variables. But what if a business method only reads them? In this situation, ejbStore() does not need to update the database.

The use of a dirty flag in ejbStore() helps optimize entity bean behavior. In the entity bean, the bean developer includes a dirty flag as an instance variable of the bean. It is initially set to false in either ejbLoad() (when the initial values come from the database) or in ejbCreate() (when the initial values come from the client). The business methods that modify persistent data set the dirty flag to true. Those business methods that only read persistent data do not. When the EJB container calls ejbStore(), this method checks the dirty flag and updates the database only if the persistent variables have changed.

We'll take a closer look at the dirty flag implementation when we present the code for class CustomerBean later in the chapter (see Listing 6.10 on page 233 through Listing 6.13 on page 237.)

Method Calls

Figure 6-4 shows a sequence diagram for obtaining an object that implements the home interface and using it to call method create(). First, we invoke the narrow() method of PortableRemoteObject and invoke CustomerHome's create() method (passing appropriate values for name, password, and e-mail address). The EJB container intercepts the call, instantiates an instance of CustomerBean (the bean implementation class), and invokes the bean's ejbCreate() method. Method ejbCreate() inserts values for the new Customer into the database. These are the JDBC calls invoked through methods in the CustomerDAO implementation class. The EJB container then invokes ejbPostCreate() followed by ejbStore(). Note that ejbStore() invokes the actual DAO method to update the database only if the dirty flag (instance variable boolean dirty) is true.

Figure 6-4. Sequence Diagram Showing the Creation of a Customer Entity Bean by Invoking the Home Interface create() Method


Figure 6-5 shows a sequence diagram for obtaining a Customer entity bean with a finder method followed by calls to business methods setPassword() and getName(). In the client's call to findByPrimaryKey(), the container instantiates a CustomerBean object. Method ejbFindByPrimaryKey() returns a primary key to the EJB container. Since the EJB container oversees the life cycle of the entity bean, it may be able to use an already-existing Customer entity bean instance to return to the client. Thus, the EJB container caches entity beans in memory.

Figure 6-5. Sequence Diagram Showing the Instantiation of a Customer Entity Bean by Invoking findByPrimaryKey() Followed by Business Methods setPassword() and getName()


Once the client has an object that implements the remote interface (Customer), it can invoke business methods. Let's begin with the call to setPassword(). Note that the EJB container first invokes method ejbActivate() followed by ejbLoad() and the required database select statement. Method setPassword() updates the persistent password field and sets the dirty flag to true. When the EJB container invokes ejbStore(), this executes the database update statement which synchronizes the database with the entity bean.

Likewise, when the client invokes business method getName(), the EJB container calls ejbLoad(). (A call to ejbActivate() is not necessary here, since the bean is already activated.) The EJB container then invokes CustomerBean's getName() followed by ejbStore(). Here, we avoid executing the database update statement because the dirty flag is false. (Method getName() does not set it.)

Design Guideline

With CMP, the EJB container code can optimize caching behavior for the entity bean. With BMP, however, any optimization is left up to the bean developer. Maintaining a “dirty flag” to track changes to persistent fields can improve performance.


DAO Pattern Implementation

Rather than place JDBC calls in private methods inside the bean implementation class, we'll use a DAO to access the database. This helps isolate database dependent code and provides a flexible way to deploy alternate database implementations. The CustomerDAO pattern is equivalent to the MusicDAO implementation you've already seen in “Data Access Object Pattern” on page 119. We'll use the Cloudscape database (part of the J2EE reference implementation) for the underlying database. Figure 6-6 provides a class diagram illustrating the class relationships.

Figure 6-6. Class Relationships in DAO Pattern Implementation for Customer EJB


CustomerModel

The Customer EJB and CustomerDAO must work together to provide access to the database. To do this, we introduce class CustomerModel, which contains fields that constitute the persistent data of a Customer entity bean. Class CustomerModel is a Value Object, and we use it to transmit the persistent fields between the Customer EJB implementation class and the CustomerDAO implementation class. Note that Customer EJB and the DAO implementation classes are the only two components that use class CustomerModel. Listing 6.5 contains the source for CustomerModel.java.

Listing 6.5. CustomerModel.java
// CustomerModel.java
public class CustomerModel {

// Encapsulation of Customer EJB persistent variables
  private String customerID;        // primary key
  private String name;
  private String password;
  private String email;
  private boolean ordersPending;
  public CustomerModel(String customerID, String name,
    String password, String email, boolean ordersPending)
  {
    setCustomerID(customerID);
    setName(name);
    setPassword(password);
    setEmail(email);
    setOrdersPending(ordersPending);
  }

  public void setCustomerID(String id) {
    this.customerID = id;
  }

  public void setName(String name) {
    this.name = name;
  }

  public void setPassword(String password) {
    this.password = password;
  }

  public void setEmail(String email) {
    this.email = email;
  }

  public void setOrdersPending(boolean ordersPending) {
    this.ordersPending = ordersPending;
  }

  public String getCustomerID() {
    return customerID;
  }

  public String getName() {
    return name;
  }

  public String getPassword() {
    return password;
  }
  public String getEmail() {
    return email;
  }

  public boolean getOrdersPending() {
    return ordersPending;
  }
}

CustomerDAOSysException

Next we need a system exception class to propagate SQLExceptions from the CustomerDAO to the Customer EJB implementation class. Listing 6.6 contains the source for CustomerDAOSysException.java.

Listing 6.6. CustomerDAOSysException.java
// CustomerDAOSysException.java
// System Exception
import java.lang.RuntimeException;

// CustomerDAOSysException extends standard
// RuntimeException.
// Thrown by CustomerDAO subclasses when there is an
// unrecoverable system error (typically SQLException)

public class CustomerDAOSysException extends
         RuntimeException {

  public CustomerDAOSysException(String msg) {
    super(msg);
  }

  public CustomerDAOSysException() {
    super();
  }
}

CustomerDAO

The CustomerDAO interface defines the database access methods required by the Customer EJB bean implementation class. Listing 6.7 contains the source for CustomerDAO.java interface.

Listing 6.7. CustomerDAO.java
// CustomerDAO.java
import java.util.Collection;
import javax.ejb.CreateException;

public interface CustomerDAO {

  // generate primary key
  public String dbGetKey();

  // inserts new CustomerModel record in database
  public void dbInsertCustomer(CustomerModel data)
    throws CreateException;

  // find specified primary key
  public boolean dbSelectByPrimaryKey(String primaryKey)
    throws CustomerDAOSysException;

  // the following 'dbSelect' methods
  // return a collection of primary keys
  public Collection dbSelectByCustomerName(String name)
    throws CustomerDAOSysException;
  public Collection dbSelectAll()
    throws CustomerDAOSysException;

  // load CustomerModel data from database
  public CustomerModel dbLoadCustomer(String primaryKey)
    throws CustomerDAOSysException;

  // store CustomerModel data to database
  public void dbStoreCustomer(CustomerModel data)
    throws CustomerDAOSysException;
  // delete Customer data from database with primary key
  public void dbRemoveCustomer(String primaryKey)
    throws CustomerDAOSysException;

  // database access method required by home method
  public int dbCountTotalCustomers()
    throws CustomerDAOSysException;
}

CustomerDAOCloudscape

The CustomerDAOCloudscape class is the implementation of CustomerDAO for the Cloudscape database. You'll note that besides containing the JDBC database access code, it also calls the Cloudscape KeyGen class to obtain a primary key.

Creating a unique primary key is a nontrivial problem, since multiple clients may attempt to create new Customer entity beans simultaneously. The Cloudscape utility KeyGen.getUniversalKeyStringValue() generates primary keys unique to the database. This routine is proprietary, however. If portability is an issue (and it usually is), techniques that dole out blocks of primary keys per request provide an efficient way to generate primary keys. The current value can be stored in a database. Furthermore, many database systems support an auto-generated primary key through a stored procedure.

Design Guideline

There are several ways to tackle the problem of generating unique primary keys. If you generate your own primary keys, using blocks of keys is efficient. A scheme which simply gets the next primary key from a database might create a bottleneck with a high-usage system.


The CustomerDAOCloudscape load and store routines encapsulate Customer persistent data fields into a CustomerModel value object. These methods all exhibit the same structure we used in the MusicDAO implementation class: we call getConnection() prior to each database access and use a finally block to release JDBC resources (closing statements and result sets and releasing the connection). Let's discuss several of these methods.

The dbInsertCustomer() method (see page 223) is called from ejbCreate() to insert a new Customer in the database. It builds a database insert command from its arguments. An error during this operation generates an SQLException that we catch and rethrow as a CreateException. Attempting to insert a record with a duplicate primary key causes the database software to throw an SQLException.

We invoke method dbSelectByPrimaryKey() (see page 224) from ejbFindByPrimaryKey(). This method specifies a query to select a record with a specific primary key as follows:

select CustomerID from Customers where CustomerID = ?

We don't read the data in the result set, but simply invoke ResultSet's next() method. If this method returns true, the current row is valid; false means there are no more rows. Thus, if our query returns false, then a record matching the specified primary key does not exist in the database.

We invoke method dbSelectByCustomerName() (see page 225) from ejbFindByCustomerName(). Here we specify the following query to select all records that match the specified name and fetch only the primary key from the database record.

select distinct CustomerID from Customers where Name = ?

Note that this method builds an ArrayList of primary keys, which we return to the finder method (and to the container). It is the EJB container's job to return the matching entity beans to the client.

Finally, look at method dbLoadCustomer() (see page 227) which we invoke from ejbLoad(). Here our select statement requests the named fields from the record that matches the specified primary key.

Select Name, Password, Email, OrdersPending from Customers
Where CustomerID = ?

We then extract each value from the result set and assign it to the matching persistent variable in value object CustomerModel.

Listing 6.8 contains the source for CustomerDAOCloudscape.java.

Listing 6.8. CustomerDAOCloudscape.java
// CustomerDAOCloudscape.java
import java.util.*;
import javax.ejb.*;
import javax.naming.*;
import javax.sql.DataSource;
import java.sql.*;
import COM.cloudscape.util.KeyGen;
// Implements CustomerDAO for Cloudscape database.
public class CustomerDAOCloudscape implements CustomerDAO {

  private Connection con = null;
  private DataSource datasource = null;

  public CustomerDAOCloudscape()
        throws CustomerDAOSysException {
    String dbName = "java:comp/env/jdbc/CustomerDB";
    try {
      InitialContext ic = new InitialContext();
      datasource = (DataSource) ic.lookup(dbName);

    } catch (NamingException ex) {
      throw new CustomerDAOSysException(
        "Cannot connect to database: " +
        dbName + ":
" + ex.getMessage());
    }
  }

  // Cloudscape propriety routine to generate
  // primary key
  // called by ejbCreate()
  public String dbGetKey() {
    return KeyGen.getUniversalKeyStringValue();
  }

  // Obtain a JDBC Database connection
  private void getConnection()
        throws CustomerDAOSysException {
    try {
      con = datasource.getConnection();
    } catch (SQLException ex) {
      throw new CustomerDAOSysException(
        "Exception during DB Connection:
"
        + ex.getMessage());
    }
  }
  // Release JDBC Database connection
  private void disConnect()
        throws CustomerDAOSysException {
    try {
      if (con != null) con.close();
    } catch (SQLException ex) {
      throw new CustomerDAOSysException(
        "SQLException during DB close connection:
" +
        ex.getMessage());
    }
  }

  private void closeStatement(PreparedStatement s)
        throws CustomerDAOSysException {
    try {
      if (s != null) s.close();
    } catch (SQLException ex) {
      throw new CustomerDAOSysException(
        "SQL Exception during statement close
"
        + ex.getMessage());
    }
  }

  private void closeResultSet(ResultSet rs)
        throws CustomerDAOSysException {
    try {
      if (rs != null) rs.close();
    } catch (SQLException ex) {
      throw new CustomerDAOSysException(
        "SQL Exception during result set close
"
        + ex.getMessage());
    }
  }

  // called by ejbCreate()
  public void dbInsertCustomer(CustomerModel newData)
      throws CreateException {

    String insertStatement =
      "insert into Customers values " +
      "( ? , ? , ? , ?, ? )";
    PreparedStatement prepStmt = null;
    try {
      getConnection();
      prepStmt =
        con.prepareStatement(insertStatement);

      prepStmt.setString(1, newData.getCustomerID());
      prepStmt.setString(2, newData.getName());
      prepStmt.setString(3, newData.getPassword());
      prepStmt.setString(4, newData.getEmail());
      prepStmt.setBoolean(5, newData.getOrdersPending());
      prepStmt.executeUpdate();

    } catch (SQLException ex) {
      throw new CreateException(ex.getMessage());
    } finally {
      closeStatement(prepStmt);
      disConnect();
    }
  }

  // called by ejbFindByPrimaryKey()
  public boolean dbSelectByPrimaryKey(String primaryKey)
        throws CustomerDAOSysException {

    String selectStatement =
      "select CustomerID " +
      "from Customers where CustomerID = ? ";

    PreparedStatement prepStmt = null;
    ResultSet rs = null;
    boolean result = false;
    try {
      getConnection();
      prepStmt =
        con.prepareStatement(selectStatement);
      prepStmt.setString(1, primaryKey);
      rs = prepStmt.executeQuery();
      result = rs.next();

    } catch (SQLException ex) {
      throw new CustomerDAOSysException(
        "dbSelectByPrimaryKey: SQL Exception caught
"
        + ex.getMessage());
    } finally {
      closeResultSet(rs);
      closeStatement(prepStmt);
      disConnect();
    }
    return result;
  }

  // called by ejbFindByCustomerName()
  public Collection dbSelectByCustomerName(String name)
        throws CustomerDAOSysException {

    System.out.println("dbSelectByCustomerName for "
       + name);

    String selectStatement =
      "select distinct CustomerID " +
      "from Customers where Name = ? ";

    PreparedStatement prepStmt = null;
    ResultSet rs = null;
    ArrayList a = null;
    try {
      getConnection();
      prepStmt =
        con.prepareStatement(selectStatement);

      prepStmt.setString(1, name);
      rs = prepStmt.executeQuery();
      a = new ArrayList();

      while (rs.next()) {
        a.add(new String(rs.getString(1)));
      }

    } catch (SQLException ex) {
      throw new CustomerDAOSysException(
        "dbSelectByCustomerName: SQL Exception caught
"
        + ex.getMessage());
    } finally {
      closeResultSet(rs);
      closeStatement(prepStmt);
      disConnect();
    }
    return a;
  }

  // called by ejbFindAll()
  public Collection dbSelectAll()
        throws CustomerDAOSysException {

    String selectStatement =
      "select distinct CustomerID " +
      "from Customers ";
    PreparedStatement prepStmt = null;
    ResultSet rs = null;
    ArrayList a = null;

    try {
      getConnection();
      prepStmt =
        con.prepareStatement(selectStatement);

      rs = prepStmt.executeQuery();
      a = new ArrayList();

      while (rs.next()) {
        a.add(new String(rs.getString(1)));
      }

    } catch (SQLException ex) {
      throw new CustomerDAOSysException(
        "dbSelectAll: SQL Exception caught
"
        + ex.getMessage());

    } finally {
      closeResultSet(rs);
      closeStatement(prepStmt);
      disConnect();
    }
    return a;
  }
  // called by ejbLoad()
  public CustomerModel dbLoadCustomer(String primaryKey)
      throws CustomerDAOSysException,
      NoSuchEntityException {
    String selectStatement =
      "Select Name, Password, Email, OrdersPending " +
      "from Customers " +
      "Where CustomerID = ? ";

    PreparedStatement prepStmt = null;
    ResultSet rs = null;
    String name = null;
    String password = null;
    String email = null;
    boolean ordersPending = false;

    try {
      getConnection();
      prepStmt =
        con.prepareStatement(selectStatement);
      prepStmt.setString(1, primaryKey);
      rs = prepStmt.executeQuery();


      if (rs.next()) {
        // Read the data from the resultset
        name = rs.getString(1);            // Name
        password = rs.getString(2);        // Password
        email = rs.getString(3);           // Email
        ordersPending = rs.getBoolean(4);  // OrdersPending
      }

      else {
        throw new NoSuchEntityException(
          "Row for customerID " + primaryKey +
          " not found in database.");
      }

      System.out.println("dbLoadCustomer: " + name);
      // Create CustomerModel to return to CustomerEJB
      return new CustomerModel(primaryKey,
                 name, password, email, ordersPending);
    } catch (SQLException ex) {
      throw new CustomerDAOSysException(
        "dbLoadCustomer: SQL Exception caught
"
        + ex.getMessage());
    } finally {
      closeResultSet(rs);
      closeStatement(prepStmt);
      disConnect();
    }
  }

  // called by ejbStore()
  public void dbStoreCustomer(CustomerModel data)
      throws CustomerDAOSysException {
    System.out.println("dbStoreCustomer: "
          + data.getName());
    String updateStatement =
      "update Customers set " +
      "Name = ? , " +
      "Password = ? , " +
      "Email = ? , " +
      "OrdersPending = ? " +
      "where CustomerID = ?";

    PreparedStatement prepStmt = null;
    try {
      getConnection();
      prepStmt =
        con.prepareStatement(updateStatement);

      // Grab values from persistent fields
      // to store in database
      prepStmt.setString(1, data.getName());
      prepStmt.setString(2, data.getPassword());
      prepStmt.setString(3, data.getEmail());
      prepStmt.setBoolean(4, data.getOrdersPending());
      prepStmt.setString(5, data.getCustomerID());
      int rowCount = prepStmt.executeUpdate();
      if (rowCount == 0) {
        throw new SQLException(
            "Storing row for CustomerID " +
            data.getCustomerID() + " failed.");
      }

    } catch (SQLException ex) {
      throw new CustomerDAOSysException(
        "dbStoreCustomer: SQL Exception caught
"
        + ex.getMessage());
    } finally {
      closeStatement(prepStmt);
      disConnect();
    }
  }

  // called by ejbRemove()
  public void dbRemoveCustomer(String primaryKey)
        throws CustomerDAOSysException {
    String removeStatement =
      "delete from Customers where CustomerID = ?";
    PreparedStatement prepStmt = null;
    try {
      getConnection();
      prepStmt =
        con.prepareStatement(removeStatement);

      prepStmt.setString(1, primaryKey);
      int result = prepStmt.executeUpdate();

      if (result == 0) {
        throw new SQLException("Remove for CustomerID " +
          primaryKey + " failed.");
      }
    } catch (SQLException ex) {
      throw new CustomerDAOSysException(
        "dbRemoveCustomer: SQL Exception caught
"
        + ex.getMessage());
    } finally {
      closeStatement(prepStmt);
      disConnect();
    }
  }

  // Use a Select query to count
  // the number of records in the database
  // called by ejbHomeGetTotalCustomers()
  public int dbCountTotalCustomers()
        throws CustomerDAOSysException {

    String selectStatement =
      "select distinct CustomerID " +
      "from Customers ";
    PreparedStatement prepStmt = null;
    ResultSet rs = null;
    int count = 0;

    try {
      getConnection();
      // Request a resultset that is scrollable
      // so we can easily count rows
      prepStmt =
        con.prepareStatement(selectStatement,
          ResultSet.TYPE_SCROLL_INSENSITIVE,
          ResultSet.CONCUR_UPDATABLE);
      rs = prepStmt.executeQuery();

      // go to the last row
      // and get its row number
      rs.last();
      count = rs.getRow();

    } catch (SQLException ex) {
      throw new CustomerDAOSysException(
        "dbCountTotalCustomers: SQL Exception caught
"
        + ex.getMessage());
    } finally {
      closeResultSet(rs);
      closeStatement(prepStmt);
      disConnect();
    }
    return count;
  }
} // CustomerDAOCloudscape

JDBC 2.0 API

The JDBC 2.0 API includes result sets that are scrollable. Previously, result sets were only accessible in the forward direction. Scrollable result sets allow forward and backward reading, as well as skipping (for example, to the end). In the CustomerDAOCloudscape method dbCountTotalCustomers() (see page 230), we define a scrollable result set so we can access the last row and easily obtain the row count. Otherwise, we would have to loop through the result set and increment a counter.


CustomerDAOFactory

The CustomerDAOFactory class performs a JNDI lookup to obtain the name of the class it should instantiate for the CustomerDAO. We specify the name of the class in the deployment descriptor. Listing 6.9 contains the source for CustomerDAOFactory.java.

Listing 6.9. CustomerDAOFactory.java
// CustomerDAOFactory.java
import javax.naming.NamingException;
import javax.naming.InitialContext;

public class CustomerDAOFactory {

  // Instantiate a subclass that implements the abstract
  // CustomerDAO interface.
  // Obtain subclass name from the deployment descriptor
  public static CustomerDAO getDAO()
        throws CustomerDAOSysException {

    CustomerDAO customerDAO = null;
    String customerDAOClass =
            "java:comp/env/CustomerDAOClass";
    String className = null;

    try {
      InitialContext ic = new InitialContext();
      // Lookup value of environment entry for
      // DAO classname
      // Value is set in deployment descriptor
      className = (String) ic.lookup(customerDAOClass);

      // Instantiate implementation class
      // for the CustomerDAO interface
      customerDAO = (CustomerDAO)
                Class.forName(className).newInstance();

    } catch (Exception ex) {
      throw new CustomerDAOSysException(
        "CustomerDAOFactory.getDAO: " +
        "NamingException for <" + className +
"> DAO class : 
" + ex.getMessage());
    }
    return customerDAO;
  }
} // CustomerDAOFactory

Bean Implementation

The CustomerBean implementation class contains the implementation of the business methods as well as all the finder methods, home methods, and EJB methods. To organize the presentation of the code, we'll display it in sections as shown in Table 6.2.

Table 6.2. CustomerBean.java Code Location
Listing and Page Contents
Listing 6.10 on page 233 Persistent Variables and Business Methods
Listing 6.11 on page 235 Finder Methods
Listing 6.12 on page 237 Home Method
Listing 6.13 on page 237 EJB Methods

Since we're using a DAO, the database access code has all been gathered into the CustomerDAO implementation class, CustomerDAOCloudscape, which you've just seen.

Listing 6.10 contains the persistent variables and business methods. Note the simplicity of the business methods; they update the persistent data fields as if the in-memory data fields and the records in the database were the same! We can write the business methods this way, because the EJB container calls ejbLoad() before each business method and ejbStore() afterwards. Note also that the business methods that modify persistent variables set the dirty flag.

Listing 6.10. CustomerBean.java—Persistent Variables and Business Methods
// CustomerBean.java
import java.sql.*;
import javax.sql.*;
import java.util.*;
import javax.ejb.*;
import javax.naming.*;

public class CustomerBean implements EntityBean {

  // Persistent data
  CustomerModel customerData;

  // Variable for DAO access
  private CustomerDAO customerDAO = null;

  // EJB variables
  private EntityContext context;
  private boolean dirty;
  // Business methods

  public String getCustomerID() {
    return customerData.getCustomerID();
  }

  public String getName() {
    return customerData.getName();
  }

  public void setName(String name) {
    customerData.setName(name);
    dirty = true;
  }

  public String getPassword() {
    return customerData.getPassword();
  }

  public void setPassword(String newPassword) {
    customerData.setPassword(newPassword);
    dirty = true;
  }

  public String getEmail() {
    return customerData.getEmail();
  }

  public void setEmail(String newEmail) {
    customerData.setEmail(newEmail);
    dirty = true;
  }

  public boolean getOrdersPending() {
    return customerData.getOrdersPending();
  }

  public void setOrdersPending(boolean pending) {
    customerData.setOrdersPending(pending);
    dirty = true;
  }

The finder methods call methods in the CustomerDAO interface to carry out the database select queries. Listing 6.11 contains the finder methods from file CustomerBean.java. Method ejbFindByPrimaryKey() returns the primary key; all the other finder methods return a collection of primary keys (or an empty collection if no database records are returned in the select query).

Again, the EJB container's role is significant here and helps explain the implementation code for finder methods. When a finder method returns a primary key, the EJB container matches the entity bean (possibly already instantiated in the container's cache of active entity beans) with the primary key. The EJB container, then, can simply return the cached bean to the client. Thus, by having the implementation code return a primary key, the EJB container is able to optimize an entity bean's life cycle. If the entity bean is not already activated, the EJB container invokes the ejbActivate() method, which causes ejbLoad() to read the persistent data from the database into the newly activated entity bean.

Listing 6.11. CustomerBean.java—Finder Methods
// Finder methods
// returns a primary key
// or throws ObjectNotFoundException

public String ejbFindByPrimaryKey(String primaryKey)
  throws FinderException {

  boolean result;

  try {
    CustomerDAO dao = getDAO();
    result = dao.dbSelectByPrimaryKey(primaryKey);
  } catch (CustomerDAOSysException ex) {
    ex.printStackTrace();
    throw new EJBException("ejbFindByPrimaryKey: " +
        ex.getMessage());
  }
  if (result) {
    return primaryKey;
  }
  else {
    throw new ObjectNotFoundException
         ("Row for id " + primaryKey + " not found.");
  }
}
  // returns a Collection of primary keys
  // or an empty Collection if no objects are found

  public Collection ejbFindByCustomerName(String name)
    throws FinderException {
    System.out.println("ejbFindByCustomerName: name="
          + name);
    Collection result;

    try {
      CustomerDAO dao = getDAO();
      result = dao.dbSelectByCustomerName(name);
    } catch (CustomerDAOSysException ex) {
      ex.printStackTrace();
      throw new EJBException("ejbFindByCustomerName: " +
        ex.getMessage());
    }
    // if we don't find any matching objects,
    // result is an empty collection
    return result;
  }

  // returns a Collection of primary keys
  // or an empty Collection if no objects are found

  public Collection ejbFindAll()
    throws FinderException {

    System.out.println("ejbFindAll()");
    Collection result;

    try {
      CustomerDAO dao = getDAO();
      result = dao.dbSelectAll();
    } catch (CustomerDAOSysException ex) {
      ex.printStackTrace();
      throw new EJBException("ejbFindAll: " +
        ex.getMessage());
    }
    // if we don't find any matching objects,
    // result is an empty collection
    return result;
  }

Listing 6.12 contains the home method in CustomerBean.java. The client invokes this home method through the home (or local home) interface. While these methods access the database, they don't cause the EJB container to instantiate entity beans like the finder methods.

Listing 6.12. CustomerBean.java—Home Method
// EJB Home Method
public int ejbHomeGetTotalCustomers() {
  System.out.println("ejbHomeGetTotalCustomers()");
  int result;

  try {
    CustomerDAO dao = getDAO();
    result = dao.dbCountTotalCustomers();
  } catch (CustomerDAOSysException ex) {
    ex.printStackTrace();
    throw new EJBException("ejbHomeGetTotalCustomers: "
        + ex.getMessage());
  }
  return result;
}

Listing 6.13 contains the EJB methods. We've placed several System.out.println statements in some of the methods. This not only helps test our EJB implementation code, but also shows when the EJB container invokes these methods (since the client does not invoke them directly).

Note that ejbStore() does not call the DAO method dbStoreCustomer() unless the boolean flag dirty is true. Recall that only the business methods that modify persistent variables (the setters) set the dirty flag to true.

The EJB container calls ejbLoad() before executing a business method. If the entity bean is being activated (after a finder call) then ejbLoad() initializes the persistent variables. Otherwise, it just refreshes them. The call to the bean context's getPrimaryKey() method ensures that we obtain a valid primary key.

Listing 6.13. CustomerBean.java—EJB Methods
// EJB Methods

// Call the Factory to get the
// CustomerDAO implementation class only
// when necessary; called by EJB Methods
private CustomerDAO getDAO()
             throws CustomerDAOSysException {
  if (customerDAO == null) {
    customerDAO = CustomerDAOFactory.getDAO();
  }
  return customerDAO;
}

public String ejbCreate(String name, String password,
  String email) throws CreateException {

  System.out.println("ejbCreate(), name=" + name);
  CustomerModel newCust = null;

  try {
    CustomerDAO dao = getDAO();
    String newKey = dao.dbGetKey();
    newCust = new CustomerModel(
          newKey, name, password, email, false);
    dao.dbInsertCustomer(newCust);

  } catch (CustomerDAOSysException ex) {
    throw new CreateException("ejbCreate: " +
        ex.getMessage());
  }
  // initialize persistent data
  customerData = newCust;
  dirty = false;
  return customerData.getCustomerID();
}

public void setEntityContext(EntityContext context) {
  this.context = context;
  System.out.println("setEntityContext()");
}

public void unsetEntityContext() {
  System.out.println("unsetEntityContext()");
}
// invoked by container just after activation
public void ejbActivate() {
  System.out.println("ejbActivate()");
}

// invoked by container just before passivation
public void ejbPassivate() {
  customerDAO = null;
}

public void ejbLoad() {
  System.out.println("ejbLoad()");
  try {
    CustomerDAO dao = getDAO();
    // Get the primary key from the context
    // and refresh persistent data from database
    customerData = dao.dbLoadCustomer(
             (String)context.getPrimaryKey());
    dirty = false;

  } catch (CustomerDAOSysException ex) {
    ex.printStackTrace();
    throw new EJBException("ejbLoad: " +
        ex.getMessage());
  }
}

public void ejbStore() {
  System.out.println("ejbStore(), name="
      + customerData.getName());
  try {
    if (dirty) {
      CustomerDAO dao = getDAO();
      // Synchronize the database with
      // the in-memory values of the persistent data
      dao.dbStoreCustomer(customerData);
      dirty = false;
      }
    } catch (CustomerDAOSysException ex) {
      ex.printStackTrace();
      throw new EJBException("ejbStore: " +
        ex.getMessage());
    }
  }

  public void ejbPostCreate(String name, String password,
        String email) {
    System.out.println("ejbPostCreate(), name=" + name);
  }

  public void ejbRemove() {
    System.out.println("ejbRemove()");
    try {
      CustomerDAO dao = getDAO();
      dao.dbRemoveCustomer(
               (String)context.getPrimaryKey());

    } catch (CustomerDAOSysException ex) {
      ex.printStackTrace();
      throw new EJBException("ejbRemove: " +
        ex.getMessage());
    }
  }
} // CustomerBean

Deployment Descriptor

Listing 6.14 contains some of the declarative information found in the deployment descriptor for the Customer EJB. Note that the descriptor specifies the interface names for both the local and remote interfaces, as well as the home and local home interfaces. Because Customer EJB is an entity bean, we must also specify its persistence type (we're using bean-managed persistence). Tag <prim-key-class> specifies the primary key type (here, java.lang.String). Under the <resource-ref> tag we specify the database resource name “jdbc/CustomerDB.” Under the <env-entry> tag we specify the CustomerDAO implementation class name “CustomerDAOCloudscape.”

Listing 6.14. Deployment Descriptor for Customer EJB
<ejb-jar>
 <display-name>CustomerJAR</display-name>
 <enterprise-beans>
    <entity>
      <display-name>CustomerBean</display-name>
      <ejb-name>CustomerBean</ejb-name>

       <home>CustomerHome</home>
      <remote>Customer</remote>
      <local-home>CustomerLocalHome</local-home>
      <local>CustomerLocal</local>
      <ejb-class>CustomerBean</ejb-class>
      <persistence-type>Bean</persistence-type>
      <prim-key-class>java.lang.String</prim-key-class>
      <reentrant>False</reentrant>

      <env-entry>
       <env-entry-name>CustomerDAOClass</env-entry-name>
       <env-entry-type>java.lang.String</env-entry-type>
       <env-entry-value>
            CustomerDAOCloudscape</env-entry-value>
      </env-entry>
      <security-identity>
        <description></description>
        <use-caller-identity></use-caller-identity>
      </security-identity>

       <resource-ref>
        <res-ref-name>jdbc/CustomerDB</res-ref-name>
        <res-type>javax.sql.DataSource</res-type>
        <res-auth>Container</res-auth>
        <res-sharing-scope>Shareable</res-sharing-scope>
      </resource-ref>
    </entity>
</ejb-jar>

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.191.233.43