Developing entity beans

An entity bean directly represents data stored in persistent storage, such as a database. It maps to a row or rows within a table in a relational database, or to an entity object in an object-oriented database. It can also map to one or more rows across multiple tables. In a database, a primary key uniquely identifies a row in a table. Similarly, a primary key identifies a specific entity bean instance. Each column in the relational database table maps to an instance variable in the entity bean.

Because an entity bean usually represents data stored in a database, it lives as long as the data. Regardless of how long an entity bean remains inactive, the container doesn't remove it from persistent storage.

The only way to remove an entity bean is to explicitly do so. An entity bean is removed by calling its remove() method, which removes the underlying data from the database. Or an existing enterprise application can remove data from the database.

Persistence and entity beans

All entity enterprise beans are persistent; that is, their state is stored between sessions and clients. As a bean provider, you can choose how your entity bean's persistence is implemented.

You can implement the bean's persistence directly in the entity bean class, making the bean itself responsible for maintaining its persistence. This is called bean-managed persistence.

Or you can delegate the handling of the entity bean's persistence to the EJB container. This is called container-managed persistence.

Bean-managed persistence

An entity bean with bean-managed persistence contains the code to access and update a database. That is, you, as the bean provider, write database access calls directly in the entity bean or its associated classes. Usually you write these calls using JDBC.

The database access calls can appear in the entity bean's business methods, or in one of the entity bean interface methods. (You'll read more about the entity bean interface soon.)

Usually a bean with bean-managed persistence is more difficult to write because you must write the additional data-access code. And, because you might choose to embed the data-access code in the bean's methods, it can also be more difficult to adapt the entity bean to different databases or to a different schema.

Container-managed persistence

You don't have to write code that accesses and updates databases for entity beans with container-managed persistence. Instead, the bean relies on the container to access and update the database.

Container-managed persistence has many advantages compared to bean-managed persistence:

Container-managed persistence has some limitations, however. For example, the container might load the entire state of the entity object into the bean instance's fields before it calls the ejbLoad() method. This could lead to performance problems if the bean has many fields.


Primary keys in entity beans

Each entity bean instance must have a primary key. A primary key is a value (or combination of values) that uniquely identifies the instance. For example, a database table that contains employee records might use the employee's social security number for its primary key. The entity bean modeling this employee table would also use the social security number for its primary key.

For enterprise beans, the primary key is represented by a String or Integer type or a Java class containing the unique data. This primary key class can be any class as long as that class is a legal value type in RMI_IIOP. This means the class must extend the java.io.Serializable interface, and it must implement the Object.equals(Other other) and Object.hashCode() methods, which all Java classes inherit.

The primary key class can be specific to a particular entity bean class. That is, each entity bean can define its own primary key class. Or multiple entity beans can share the same primary key class.


Writing the entity bean class

To create an entity bean class, JBuilder's Enterprise JavaBean wizard can start these tasks for you. It creates a class that extends the EntityBean interface and writes empty implementations of the EntityBean methods. You fill in the implementations if your bean requires it. The next section explains what these methods are and how they are used.

If you'd like to build entity beans using existing database tables, use JBuilder's EJB Entity Modeler. For information, see "Creating entity enterprise beans with the EJB Entity Modeler."

Extending the EntityBean interface

The EntityBean interface defines the methods all entity beans must implement. It extends the EnterpriseBean interface.
public void EntityBean extends EnterpriseBean {
    public void setEntityContext(EntityContext ctx) throws EJBException,
	    RemoteException;
    public void unsetEntitycontext() throws EJBException, RemoteException;
    void ejbRemove() throws RemoveException, EJBException, RemoteException;
    void ejbActivate() throws EJBException, RemoteException;
    void ejbPassivate() throws EJBException, RemoteException;
    void ejbLoad() throws EJBException, RemoteException;
    public void ejbStore() throws EJBException, RemoteException;
}
The methods of the EntityBean interface are closely associated with the life cycle of an entity bean. This table explains their purpose:
Method Description
setEntityContext() Sets an entity context. The container uses this method to pass a reference to the EntityContext interface to the bean instance. The EntityContext interface provides methods to access properties of the runtime context for the entity bean. An entity bean instance that uses this context must store it in an instance variable.
unsetEntityContext() Frees the resources that were allocated during the setEntityContext() method call. The container calls this method before it terminates the life of the current instance of the entity bean.
ejbRemove() Removes the database entry or entries associated with this particular entity bean. The container calls this method when a client invokes a remove() method.
ejbActivate Notifies an entity bean that it has been activated. The container invokes this method on the instance selected from the pool of available instances and assigned to a specific entity object identity. When the bean instance is activated, it has the opportunity to acquire additional resources that it might need.
ejbPassivate() Notifies an entity bean that it is about to be deactivated--that is, the instance's association with an entity object identity is about to be broken and the instance returned to the pool of available instances. The instance can then release any resources allocated with the ejbActivate() method that it might not want to hold while in the pool.
ejbLoad() Refreshes the data the entity object represents from the database. The container calls this method on the entity bean instance so that the instance synchronizes the entity state cached in its instance variables to the entity state in the database.
ejbStore() Stores the data the entity object represents in the database. The container calls this method on the entity bean instance so that the instance synchronizes the database state to the entity state cached in its instance variables.

Declaring and implementing the entity bean methods

Entity beans can have three types of methods:

Creating create methods

If you use the Enterprise JavaBean wizard to begin your enterprise bean, you'll see that the wizard adds an ejbCreate() method and an ejbPostCreate() method to the bean class that takes no parameters. You can write additional create methods if your bean requires them.

Keep in mind that entity beans are not required to have create methods. Calling a create method of an entity bean inserts new data in the database. You can have entity beans without create methods if new instances of entity objects should be added to the database only through DBMS updates or through a legacy application.

ejbCreate() method
If you choose to add additional ejbCreate() methods that include parameters, remember these rules: The signature for an ejbCreate() method is the same, regardless whether the bean uses container-managed or bean-managed persistence. This is the signature for all ejbCreate() methods of an entity bean:
public <PrimaryKeyClass> ejbCreate( <zero or more parameters> )
// implementation
}

When the client calls the create() method, the container in response executes the ejbCreate() method and inserts a record representing the entity object into the database. The ejbCreate() methods usually initialize some entity state. Therefore, they often have one or more parameters and their implementations include code that sets the entity state to the parameter values. For example, the bank example discussed later in this chapter has a checking account entity bean whose ejbCreate() method takes two parameters, a string and a float value. The method initializes the name of the account to the string value and the account balance to the float value:

public AccountPK ejbCreate(String name, float balance) {
    this.name = name;
    this.balance = balance;
    return null;
}
ejbPostCreate() method
When an ejbCreate() method finishes executing, the container then calls a matching ejbPostCreate() method to allow the instance to complete its initialization. The ejbPostCreate() matches the ejbCreate() method in its parameters, but it returns void:
public void ejbPostCreate( <zero or more parameters> )
    // implementation
}
Follow these rules when defining an ejbPostCreate(): Use ejbPostCreate() to perform any special processing your bean needs to do before it becomes available to the client. If your bean doesn't need to do any special processing, leave the method body empty, but remember to include one ejbPostCreate() for every ejbCreate() for an entity bean with bean-managed persistence.

Creating finder methods

Every entity bean must have one or more finder methods. Finder methods are used by clients to locate entity beans. Each bean-managed entity bean must have an ejbFindByPrimaryKey() method that has a corresponding findByPrimaryKey() in the bean's home interface. This is the ejbFindByPrimaryKey() method's signature:
public <PrimaryKeyClass> ejbFindByPrimaryKey(<PrimaryKeyClass primaryKey>) {
    // implementation
}
You can define additional finder methods for your bean. For example, you might have an ejbFindByLastName() method. Each finder method must follow these rules:

For entity beans with bean-managed persistence, each finder method declared in the bean class must have a corresponding finder method in the bean's home interface that has the same parameters, but returns the entity bean's remote interface. The client locates the entity bean it wants by calling the finder method of the home interface and the container then invokes the corresponding finder method in the bean class. See "Finder methods for entity beans" in the "Creating the home and remote interfaces" chapter.

Writing the business methods

Within your enterprise bean class, write full implementations of the business methods your bean needs. To make these methods available to a client, you must also declare them in the bean's remote interface using the exact same signature.

Using JBuilder wizards to create an entity bean

You can use the JBuilder's Enterprise JavaBeans wizard to quickly start the creation of an entity bean. If you have existing database tables you want to use to create enterprise beans from, start with JBuilder's EJB Entity Modeler. And if you have an existing entity bean, but don't have its home and remote interfaces, use the EJB Interfaces wizard to create them. You can then modify the code in JBuilder using the code editor and the Bean designer. Edit deployment descriptors with JBuilder's Deployment Descriptor editor. Create a test client application with the EJB Test Client Application wizard to test your new enterprise beans. Finally, use the EJB Deployment wizard to simplify the process of deploying your enterprise beans.

For more information about using JBuilder's EJB development tools to create entity beans, see "Developing enterprise beans with JBuilder," "Using the Deployment Descriptor editor," and "Deploying enterprise beans."


The life of an entity bean

There are three distinct states in the life cycle of an entity enterprise bean:

The following diagram depicts the life cycle of an entity bean instance:

The nonexistent state

At first the entity bean instance doesn't exist. The EJB container creates an instance of an entity bean and then it calls the setEntityContext() method on the entity bean to pass the instance a reference to its context; that is, a reference to the EntityContext interface. The EntityContext interface gives the instance access to container-provided services and allows it to obtain information about its clients. The entity bean is now in the pooled state.

The pooled state

Each type of entity bean has its own pool. None of the instances in the pool are associated with data. Because none of their instance variables have been set, the instances have no identity and they are all equivalent. The container is free to assign any instance to a client that requests such an entity bean.

When a client application calls one of the entity bean's finder methods, the container executes the corresponding ejbFind() method on an arbitrary instance in the pool. The instance remains in the pooled state during the execution of a finder method.

When the container selects an instance to service a client's requests to an entity object, that instance moves from the pooled to the ready state. There are two ways that an entity instance moves from the pooled state to the ready state:

The container selects the instance to handle a client's create() request on the bean's home interface. In response to the create() call, the container creates an entity object and calls the ejbCreate() and ejbPostCreate() methods when the instance is assigned to the entity object.

The container calls the ejbActivate() method to activate an instance so that it can respond to an invocation on an existing entity object. Usually the container calls ejbActivate() when there is no suitable instance in the ready state to handle the client's calls.

The ready state

When the instance is in the ready state, it is associated with a specific primary key. Clients can call the application-specific methods on the entity bean. The container calls the ejbLoad() and ejbStore() methods to tell the bean to load and store its data. They also enable the bean instance to synchronize its state with that of the underlying data entity.

Returning to the pooled state

When an entity bean instance moves back to the pooled state, the instance is decoupled from the data represented by the entity. The container can now assign the instance of any entity object within the same entity bean home. There are two ways an entity bean instance moves from the ready state back to the pooled state:

To remove an unassociated instance from the pool, the container calls the instance's unsetEntityContext() method.


A bank entity bean example

The bank example shows you how to use entity beans. It includes two implementations of the same Account remote interface. One implementation uses bean-managed persistence, and the other uses container-managed persistence.

The SavingsAccount entity bean, which uses bean-managed persistence, models savings accounts. As you examine the entity bean code, you'll see that it includes direct JDBC calls.

The CheckingAccount entity bean, which uses container-managed persistence, models checking accounts. It relies on the container to implement persistence, not you, the bean developer.

A third enterprise bean called Teller transfers funds from one account to the other. It's a stateless session bean that shows you how calls to multiple entity beans can be grouped within a single container-managed transaction. Even it the credit occurs before the debit in the transfer operation, the container rolls back the transaction if the debit fails, and neither the debit nor the credit occurs.

You can find complete code for the bank example in the /Inprise/AppServer/examples/ejb/bank directory.

The entity bean home interface

Multiple entity beans can share the same home and remote interfaces, even if one entity bean uses container-managed persistence and the other uses bean-managed persistence. Both SavingsAccount and CheckingAccount entity beans use the same home interface, AccountHome. They also use the same Account remote interface.

The home interface for an entity bean is very much like the home interface for a session bean. They extend the same javax.ejb.EJBHome interface. The home interface for entity beans must include at least one finder method. A create() method is optional.

Here is the code for the AccountHome interface:

public interface AccountHome extends javax.ejb.EJBHome {
  Account create(String name, float balance)
    throws java.rmi.RemoteException, javax.ejb.CreateException;
  Account findByPrimaryKey(AccountPK primaryKey)
    throws java.rmi.RemoteException, javax.ejb.FinderException;
  java.util.Enumeration findAccountsLargerThan(float balance)
    throws java.rmi.RemoteException, javax.ejb.FinderException:
}
The AccountHome home interface implements three methods. While the create() method is not required for entity beans, the bank example does implement one. The create() method inserts a new entity bean object into the underlying database. You could choose to defer the creation of new entity objects to the DBMS or to another application, in which case you would not define a create() method.

The create() method requires two parameters, an account name string and a balance amount. The implementation of this method in the entity bean class uses these two parameter values to initialize the entity object state--the account name and the starting balance--when it creates a new object. The throws clause of a create() method must include the java.rmi.RemoteException and the java.ejb.CreateException. It can also include additional application-specific exceptions.

Entity beans must have the findByPrimaryKey() method, so the AccountHome interface declares this method. It takes one parameter, the AccountPK primary key class, and returns a reference to the Account remote interface. This method finds one particular account entity and returns a reference to it.

Although it's not required, the home interface also declares a second finder method, findAccountsLargerThan(). This method returns a Java Enumeration containing all the accounts whose balance is greater than some amount.

The entity bean remote interface

More than one entity bean can use the same remote interface, even when the beans use different persistence management strategies. The bank example's two entity beans both use the same Account remote interface.

The remote interface extends the javax.ejb.EJBObject interface and exposes the business methods that are available to clients. Here is the code:

public interface Account extends javax.ejb.EJBObject {
  public float getBalance() throws java.rmi.RemoteException;
  public void credit(float amount) throws java.rmi.RemoteException;
  public void debit(float amount) throws java.rmi.RemoteException;
}

An entity bean with container-managed persistence

The bank example implements a CheckingAccount entity bean that illustrates the basics for using container-managed persistence. In many ways, this implementation is like a session bean implementation. There are some key things to note in the implementation of an entity bean that uses container-managed persistence, however:
import javax.ejb.*
import java.rmi.RemoteException;

public class CheckingAccount implements EntityBean {
  private javax.ejb.EntityContext _context;
  public String name;
  public float balance;

  public float getBalance() {
    return balance;
  }

  public void debit(float amount) {
    if(amount > balance) {
      // mark the current transaction for rollback ...
     _context.setRollbackOnly();
    }
    else {
      balance = balance - amount;
    }
  }

  public void credit(float amount) {
    balance = balance + amount;
  }

  public AccountPK ejbCreate(String name, float balance) {
    this.name = name;
    this.balance = balance;
    return null;
  }

  public void ejbPostCreate(String name, float balance) {}
  public void ejbRemove() {}
  public void setEntityContext(EntityContext context) {
    _context = context;
  }

  public void unsetEntityContext() {
    context = null;
  }

  public void ejbActivate() {}
  public void ejbPassivate() {}
  public void ejbLoad() {}
  public void ejbStore() {}
  public String toString() {
    return"CheckingAccount[name=" + name + ",balance=" + balance +"]";
  }
}

An entity bean with bean-managed persistence

The bank example also implements a SavingsAccount bean, an entity bean with bean-managed persistence. The SavingsAccount bean accesses a different account table than the CheckingAccount bean. Although these two entity beans use different persistence-management approaches, they can both use the same home and remote interfaces. There are differences between the two implementations, however.

An entity bean implementation with bean-managed persistence does the following:

The following code sample shows the interesting code portions from the SavingsAccount implementation class. The example removes the code that is merely the empty implementations of the EntityBean interfaces methods, such as ejbActivate(), ejbPassivate(), and so on.

First look at the ejbLoad() method, which accesses the database entity object, to see how a bean with bean-managed persistence implements database access. Note that all of the methods implemented in the SavingsAccount class follow the same approach as ejbLoad() uses. The ejbLoad() method begins by establishing a connection to the database. It calls the internal getConnection() method, which uses a DataSource to obtain a JDBC connection to the database from a JDBC connection pool. Once the connection is established, ejbLoad() creates a PreparedStatement object and builds its SQL database access statement. Because ejbLoad() reads the entity object values into the entity bean's instance variables, it builds an SQL SELECT statement for a query that selects the balance value for the savings account whose name matches a pattern. The method then executes the query. If the query returns a result, it extracts the balance amount. The ejbLoad() method finished by closing the PreparedStatement objects and then closing the database connection. Note that the ejbLoad() method doesn't actually close the connection. Instead, it simply returns the connection to the connection pool.

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

public class SavingsAccount implements EntityBean {
  private entitycontext _constext;
  private String _name;
  private float _balance;

  public float getBalance() {
    return _balance;
  }

  public void debit(float amount) {
    if(amount > balance) {
      // mark the current transaction for rollback...
     _context.setRollbackOnly();
    } else {
        _balance = _balance - amount;
    }
  }
  public void credit(float amount) {
    _balance = _balance + amount;
  }

  // setEntitycontext(), unsetEntityContext(), ejbActivate(), ejbPassivate(),
  // ejbPostCreate() skeleton implementations are not shown here
  ...
  
  public AccountPK ejbCreate(String name, float balance)
      throws RemoteException, CreateException {
    _name = name;
    _balance = balance;
    try {
      Connection connection = getConnection();
      PreparedStatement statement = connection.prepareStatement
        ("INSERT INTO Savings_Accounts (name, balance) VALUES (?,?)*);
      statement.setString(1, _name);
      statement.setFloat(2, _balance);
      if(statement.executeUpdate() != 1) {
        throw new CreateException("Could not create: " + name);
      }
      statement.close();
      connection.close();
      return new AccountPK(name);
    } catch(SQLException e) {
      throw new RemoteException("Could not create: " + name, 3);
    }
  }
  ...
  public void ejbRemote() throws RemoteException, RemoveException {
    try {
      Connection connection = getConnection();
      PreparedStatement statement = connection.prepareStatement
        ("DELETE FROM Savings Account WHERE name = ?");
      statement.setString(1, _name);
      if(statement.executeUpdate() != 1) {
        throw new RemoteException("Could not remove: " + _name, e);
      }
      statement.close();
      connection.close();
    } catch(SQLException e) {
      throw new RemoteException("Could not remove: " + _name, e);
    }
  }
  public AccountPK ejbFindByPrimaryKey(AccountPK key) throws RemoteException,
        FinderException {
    try {
      Connection connection = getConnection();
      PreparedStatement statement = connection.prepareStatement
        ("SELECT name FROM Savings_Accounts WHERE name = ?");
      statement.setString(1, key.name);
      ResultSet resultSet = statement.executeQuery();
      if(!resultSet.next()) {
        throw new FinderException("Could not find: " + key
      statement.close();
      connection.close();
      return key;
    } catch(SQLException e) {
        throw new RemoteException("Could not find: " + key, e);
    }
  }

  public java.util.Enumeration ejbFindAccountsLargerThan(float balance)
      throws RemoteException, FinderException {
    try {
      Connection connection = getConnection();
      PreparedStatement statement = connection.prepareStatement
        ("SELECT name FROM Savings_Account WHERE balance > ?");
      statement.setFloat(1, balance);
      ResultSet resultSet = statement.executeQuery();
      Vector keys = new Vector();
      while(resultSet.next()) {
        String name = resultSet.getString(1);
        keys.addElement(new AccountPK(name));
      }
      statement.close();
      connection.close();
      return keys.elements();
    } catch(SQLException 3) {
      throw new RemoteException("Could not findAccountsLargerThan: " + balance, e);
    }
  }

  public void ejbLoad() throws RemoteException {
    // get the name from the primary key
    _name = (AccountPK) _context.getPrimaryKey()).name;
    try {
      Connection connection = getConnection();
      PreparedStatement statement = connection.prepareStatement
        ("SELECT balance FROM Savings_Account WHERE name = ?");
      statement.setString(1, _name);
      ResultSet resultSet = statement.executeQuery();
      if(!resultSet.next()) {
        throw new RemoteException("Account not found: " + _name);
      }
      _balance = resultSet.getFloat(1);
      statement.close();
      connection.close();
    } catch(SQLException e) {
        throw new RemoteException("Could not load: " + _name, e):
    }
  }

  public void ejbStore() throw RemoteException {
     try {
      Connection connection = getConnection();
      PreparedStatement statement = connection.prepareStatement
        ("UPDATE Savings_Accounts SET balance = ? WHERE name = ?");
      statement.setFloat(1, _balance);
      statement.setString(2, _name);
      statement.executeUpdate();
      statement.close();
      connection.close();
    } catch(SQLException e) {
      throw new RemoteException("Could not store: " + _name, e);
    }
  }

  private connection getconnection() throws SQLException {
    Properties properties = _context.getEnvironment();
    String url = properties.getProperty("db.url");
    String username = properties.getProperty("db.username");
    String password = properties.getProperty("db.password");
    if(username != null) {
      return DriverManager.getConnection(url, username, password);
    } else {
      return DriverManager.getConnection(url);
    }
  }

  public String toString() {
    return "SavingsAccount[name=" + _name + ",balance=" + _balance +"]";
  }
}

The primary key class

Both CheckingAccount and SavingsAccount use the same field to uniquely identify a particular account record. In this case, they both use the same primary key class, AccountPK, to represent the unique identifier for either type of account:
public class AccountPK implements java.io.Serializable {
  public String name;
  public AccountPK() {}
  public AccountPK(String name) {
     this.name = name;
  }
}

The deployment descriptor

The deployment descriptor for the bank example deploys three kinds of beans: the Teller session bean, the CheckingAccount entity bean with container-managed persistence, and the SavingsAccount entity bean with bean-managed persistence.

You use properties in the deployment descriptor to specify information about the entity bean's interfaces, transaction attributes, and so on, just as you do for session beans. But you also add additional information that is unique to entity beans.

The bean-managed XML code sample shows typical deployment descriptor property tags for an entity bean with bean-managed persistence. This container-managed XML code sample illustrates the typical deployment descriptor tags for an entity bean that uses container-managed persistence. When you compare the descriptor tags for the two types of entity beans, you'll notice that the deployment descriptor for an entity bean with container-managed persistence is more complex.

The bean's deployment descriptor type is set to <entity>. Notice that the first tags within the <enterprise-beans> section in both code samples specify that the bean is an entity bean.

An entity bean deployment descriptor specifies the following type of information:

Information about the container-managed fields for container-managed beans. The container uses this information to generate the finder methods for these fields.

Deployment descriptor for an entity bean with bean-managed persistence

The following code sample shows the key parts of the deployment descriptor for an entity bean using bean-managed persistence. Because the bean, not the container, handles its own fetches from the database entity values and updates to these values, the descriptor doesn't specify fields for the container to manage. Nor does it tell the container how to implement its finder methods, because the bean's implementation provides those.
<enterprise-beans>
<entity>
  <description>This entity bean is an example of bean-managed persistence</description>
  <ejb-name>savings</ejb-name>
  <home>AccountHome</home>
  <remote>Account</remote>
  <ejb-class>SavingsAccount</ejb-class>
  <persistence-type>Bean</persistence-type>
  <prim-key-class>AccountPK</prim-key-class>
  <reentrant>False</reentrant>
</entity>
  ...
</enterprise-beans>
<assembly-descriptor>
  <container-transaction>
    <method>
      <ejb-name>savings</ejb-name>
      <method-name>*</method-name>
    </method>
    <trans-attribute>Required</trans-attribute>
  </container-transaction>
</assembly-descriptor>

Deployment descriptor for an entity bean with container-managed persistence

The next code sample shows the key parts of the deployment descriptor for an entity bean using container-managed persistence. Because the bean lets the container handle loading database entity values and updating these values, the descriptor specifies the fields that the container will manage.
<enterprise-beans>
<entity>
  <description>This entity bean is an example of container-managed persistence</description>
  <ejb-name>checking</ejb-name>
  <home>AccountHome></home>
  <remote>Account</remote>
  <ejb-class>chkingAccount</ejb-class>
  <persistence-type>Container</persistence-type>
  <prim-key-class>AccountPK>/prim-key-class>
  <reentrant>False</reentrant>
  <cmp-field>
    <field-name>name</field-name>
  <cmp-field>
</entity>
</enterprise-beans>
<assembly-descriptor>
  <container-transaction>
    <method>
      <ejb-name>chekcing</ejb-name>
      <method-name>*</method-name>
    </method>
    <trans-attribute-transaction>
  </container-transaction>
</assembly-descriptor>