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.
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.
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 has many advantages compared to bean-managed persistence:
ejbLoad()
method. This could lead to performance problems if the bean has many fields.
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.
javax.ejb.EntityBean
interface.
ejbCreate()
methods. If you've already created the home interface for the bean, the bean must have an ejbCreate()
method with the same signature for each create()
method in the home interface.
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."
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. |
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()
methods that include parameters, remember these rules:
ejbCreate()
must be declared as public.
ejbCreate()
method must return null.
The container has complete responsibility for creating container-managed entity beans.
ejbCreate()
method must return an instance of the primary key class for the new entity object.
The container uses this primary key to create the actual entity reference.
ejbCreate()
method must be of the same number and type as those in the corresponding create()
method in the bean's remote interface.
ejbCreate()
method must have a corresponding ejbPostCreate()
method that matches the ejbCreate(
) in the same number of parameters.
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; }
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()
:
ejbCreate()
method.
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.
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.
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 following diagram depicts the life cycle of an entity bean instance:
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.
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:
ejbCreate()
and ejbPostCreate()
methods.
ejbActivate()
method.
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.
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.
ejbPassivate()
method to disassociate the instance from its primary key without removing the underlying entity object.
ejbRemove()
method to remove the entity object. It calls ejbRemove()
when the client application calls the bean's home or remote remove()
method.
To remove an unassociated instance from the pool, the container calls the instance's unsetEntityContext()
method.
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.
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.
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; }
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:
CheckingAccount
bean declares name
and balance
to be public fields.
EntityBean
interface: ejbActivate()
, ejbPassivate()
, ejbLoad()
, ejbStore()
, ejbRemove()
, setEntityContext()
, and unsetEntityContext()
. The entity bean is required to provide skeletal implementations of these methods only, however, although it is free to add application-specific code where it is appropriate. The CheckingAccount
bean saves the context returned by setEntityContext()
and releases the reference in unsetEntityContext()
. Otherwise, it adds no additional code to the EntityBean
interface methods.
CheckingAccount
includes an implementation of the ejbCreate()
method because this enterprise bean allows callers to create new checking accounts. The implementation also initializes the instance's two variables, name
and balance
, to the parameter values. ejbCreate()
returns a null value because, with container-managed persistence, the container creates the appropriate reference to return to the client.
CheckingAccount
provides the minimal implementation of the ejbPostCreate()
method, although this method could have performed further initialization work if it was needed. For beans with container-managed persistence, you need just a minimal implementation of ejbPostCreate()
because it serves as a notification callback.
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 +"]"; } }
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 bean includes code to access these variables, to load the database values into these instance variables, and to store their changes to the database. As such, the bean can limit access to these variables as it sees fit. This differs from a bean using container-managed persistence, which must declare all container-managed variables to be public so that the container can access them.
ejbCreate()
method returns the primary key class.
In the SavingsAccount
bean the class is AccountPK
. The container takes the returned primary key class and uses it to construct a remote reference to the entity bean instance.
ejbCreate()
method.
The SavingsAccount
bean doesn't need to include additional initialization code in this method.
ejbLoad()
and ejbStore()
methods.
A bean using container-managed persistence usually provides just an empty implementation of these methods because the container handles persistence. An entity bean with bean-managed persistence must provide its own code to read the database values into its instance variables in the ejbLoad()
method, and to write to the database with changed values in the ejbStore()
method.
The SavingsAccount
entity bean implements two finder methods, the required ejbFindByPrimaryKey()
method, and the optional ejbFindAccountsLargerThan()
method.
ejbRemove()
method.
Because the bean is managing the underlying database entity object, it must implement this method so that it can remove the entity object from the database. A bean with container-managed persistence will omit the implementation of this method because the container is responsible for the database management.
These methods are ejbCreate()
, ejbRemove()
, ejbLoad()
, ejbStore
, ejbFindByPrimaryKey()
, all other finder methods, and the business methods. Each method contains code to connect to the database, followed by code to build and then execute SQL statements that accomplish the functionality encompassed by the method. When the SQL statements complete, the method closes the statements and the database connection before returning.
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 +"]"; } }
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; } }
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:
Each enterprise bean specifies its home interface using the <home> tag, its remote interface using the <remote> tag, and its implementation class name using the <ejb-class> tag.
This usually appears in the <assembly-descriptor> section of the deployment descriptor.
In this example, the primary key class is AccountPK
and it appears within the <prim-key-class> tag.
The CheckingAccount
bean uses container-managed persistence, so the deployment descriptor sets the <persistence-type> tag to Container.
Neither the SavingsAccount
nor the CheckingAccount
bean is reentrant, so the <reentrant> tag to set to False for both.
A bean that uses bean-managed persistence doesn't specify any container-managed fields. Therefore, the deployment descriptor for the SavingsAccount
bean doesn't specify any container-managed fields. An entity bean using container-managed persistence must specify the names of its fields or instance variables that the container must manage. Use a combination of the <cmp field> and <field name> tags for this. The first tag, <cmp field>, indicates that the field is container-managed. Within this tag, the <fields name> tag specifies the name of the field itself. For example, the CheckingAccount
bean deployment descriptor indicates that the balance field is container-managed as follows:
<cmp field><field name>balance</field name></cmp field>
Information about the container-managed fields for container-managed beans. The container uses this information to generate the finder methods for these fields.
<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>
<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>