More-advanced programming concepts for enterprise beans
This chapter discusses some of the more advanced programming concepts
associated with developing and using enterprise beans. It includes
information on developing entity beans with bean-managed persistence (BMP),
writing the code required by a BMP bean to interact with a database, and
developing session beans that directly participate in transactions.
In an entity bean with container-managed persistence (CMP), the container
handles the interactions between the enterprise bean and the data
source. In an entity bean with bean-managed persistence (BMP), the
enterprise bean must contain all of the code required for the interactions
between the enterprise bean and the data source. For this reason,
developing an entity bean with CMP is simpler than developing an entity bean
with BMP. However, you must use BMP if any of the following is true
about an entity bean:
- The bean's persistent data is stored in more than one data
source.
- The bean's persistent data is stored in a data source that is not
supported by the EJB server that you are using.
This section examines the development of entity beans with BMP. For
information on the tasks required to develop an entity bean with CMP, see Developing entity beans with CMP.
Every entity bean must contain the following basic parts:
In an entity bean with BMP, you can create your own primary key class or
use an existing class for the primary key. For more information, see Writing or selecting the primary key class (entity with BMP).
In an entity bean with BMP, the bean class defines and implements the business
methods of the enterprise bean, defines and implements the methods used to
create instances of the enterprise bean, and implements the methods invoked by
the container to move the bean through different stages in the bean's
life cycle.
By convention, the enterprise bean class is named NameBean,
where Name is the name you assign to the enterprise bean.
The enterprise bean class for the example AccountBM enterprise bean is named
AccountBMBean.
Every entity bean class with BMP must meet the following requirements:
- It must be public, it must not be abstract, and it must
implement the javax.ejb.EntityBean interface. For more
information, see Implementing the EntityBean interface.
- It must define instance variables that correspond to persistent data
associated with the enterprise bean. For more information, see Defining instance variables.
- It must implement the business methods used to access and manipulate the
data associated with the enterprise bean. For more information, see Implementing the business methods.
- It must contain code for getting connections to, interacting with, and
releasing connections to the data source (or sources) used to store the
persistent data. For more information, see Using a database with a BMP entity bean.
- It must define and implement an ejbCreate method for each way in which the
enterprise bean can be instantiated. It can, but is not required to,
define and implement a corresponding ejbPostCreate method for each ejbCreate
method. For more information, see Implementing the ejbCreate and ejbPostCreate methods.
- It must implement the ejbFindByPrimaryKey method that takes a primary key
and determines if it is valid and unique. It can also define and
implement additional finder methods as required. For more information,
see Implementing the ejbFindByPrimaryKey and other ejbFind methods.
- Note:
- The enterprise bean class can implement the enterprise bean's remote
interface, but this is not recommended. If the enterprise bean class
implements the remote interface, it is possible to inadvertently pass the
this variable as a method argument.
Figure 42 shows the import statements and class declaration for the
example AccountBM enterprise bean.
Figure 42. Code example: The AccountBMBean class
...
import java.rmi.RemoteException;
import java.util.*;
import javax.ejb.*;
import java.lang.*;
import java.sql.*;
import com.ibm.ejs.doc.account.InsufficientFundsException;
public class AccountBMBean implements EntityBean {
...
}
An entity bean class can contain both persistent and nonpersistent instance
variables; however, static variables are not supported in enterprise
beans unless they are also final (that is, they are constants).
Persistent variables are stored in a database. Unlike the persistent
variables in a CMP entity bean class, the persistent variables in a BMP entity
bean class can be private.
Nonpersistent variables are not stored in a database and are
temporary. Nonpersistent variables must be used with caution and must
not be used to maintain the state of an EJB client between method
invocations. This restriction is necessary because nonpersistent
variables cannot be relied on to remain the same between method invocations
outside of a transaction because other EJB clients can change these variables
or they can be lost when the entity bean is passivated.
The AccountBMBean class contains three instance variables that represent
persistent data associated with the AccountBM enterprise bean:
- accountId, which identifies the account ID associated with an
account
- type, which identifies the account type as either savings (1)
or checking (2)
- balance, which identifies the current balance of the account
The AccountBMBean class contains several nonpersistent instance variables
including the following:
- entityContext, which identifies the entity context of each
instance of an AccountBM enterprise bean. The entity context can be
used to get a reference to the EJB object currently associated with the bean
instance and to get the primary key object associated with that EJB
object.
- jdbcUrl, which encapsulates the database universal resource
locator (URL) used to connect to the data source. This variable must
have the following format:
dbAPI:databaseType:databaseName.
For example, to specify a database named sample in an IBM DB2 database with
the Java Database Connectivity (JDBC) API, the argument is
jdbc:db2:sample.
- driverName, which encapsulates the database driver class
required to connect to the database.
- DBLogin, which identifies the database user ID required to
connect to the database.
- DBPassword, which identifies password for the specified user ID
(DBLogin) required to connect to the database.
- tableName, which identifies the database table name in which
the bean's persistent data is stored.
- jdbcConn, which encapsulates a Java Database Connectivity
(JDBC) connection to a data source within a java.sql.Connection
object.
Figure 43. Code example: The instance variables of the AccountBMBean class
...
public class AccountBMBean implements EntityBean {
private EntityContext entityContext = null;
...
private static final String DBRULProp = "DBURL";
private static final String DriverNameProp = "DriverName";
private static final String DBLoginProp = "DBLogin";
private static final String DBPasswordProp = "DBPassword";
private static final String TableNameProp = "TableName";
private String jdbcUrl, driverName, DBLogin, DBPassword, tableName;
private long accountId = 0;
private int type = 1;
private float balance = 0.0f;
private Connection jdbcConn = null;
...
}
To make the AccountBM bean more portable between databases and database
drivers, the database-specific variables (jdbcUrl,
driverName, DBLogin, DBPassword, and
tableName) are set by retrieving corresponding environment
variables contained in the enterprise bean. The values of these
variables are retrieved by the getEnvProps method, which is implemented in the
AccountBMBean class and invoked when the setEntityContext method is
called. For more information, see Managing database connections in the EJB server environment.
Although Figure 43 shows database access compatible with version 1.0 of
the JDBC specification, you can also perform database accesses that are
compatible with version 2.0 of the JDBC specification. An
administrator binds a javax.sql.DataSource reference (which
encapsulates the information that was formerly stored in the jdbcURL and
driverName variables) into the JNDI namespace. The entity bean with BMP
does the following to get a java.sql.Connection:
DataSource ds = (dataSource)initialContext.lookup("java:comp/env/jdbc/MyDataSource");
Connection con = ds.getConnection();
where MyDataSource is the name the administrator assigned to the
datasource.
The business methods of an entity bean class define the ways in which the data
encapsulated in the class can be manipulated. The business methods
implemented in the enterprise bean class cannot be directly invoked by an EJB
client. Instead, the EJB client invokes the corresponding methods
defined in the enterprise bean's remote interface by using an EJB object
associated with an instance of the enterprise bean, and the container invokes
the corresponding methods in the instance of the enterprise bean.
Therefore, for every business method implemented in the enterprise bean
class, a corresponding method must be defined in the enterprise bean's
remote interface. The enterprise bean's remote interface is
implemented by the container in the EJB object class when the enterprise bean
is deployed.
There is no difference between the business methods defined in the
AccountBMBean bean class and those defined in the CMP bean class AccountBean
shown in Figure 10.
You must define and implement an ejbCreate method for each way in which you
want a new instance of an enterprise bean to be created. For each
ejbCreate method, you can also define a corresponding ejbPostCreate
method. Each ejbCreate method must correspond to a create method in the
EJB home interface.
Like the business methods of the bean class, the ejbCreate and
ejbPostCreate methods cannot be invoked directly by the client.
Instead, the client invokes the create method of the enterprise bean's
home interface by using the EJB home object, and the container invokes the
ejbCreate method followed by the ejbPostCreate method.
Unlike the method in an entity bean with CMP, the ejbCreate method in an
entity bean with BMP must contain all of the code required to insert the
bean's persistent data into the data source. This requirement
means that the ejbCreate method must get a connection to the data source (if
one is not already available to the bean instance) and insert the values of
the bean's variables into the appropriate fields in the data
source.
Each ejbCreate method in an entity bean with BMP must meet the following
requirements:
- It must be public and return the bean's primary key class.
- Its arguments and return type must be valid for Java remote method
invocation (RMI).
- It must contain the code required to insert the values of the persistent
variables into the data source. For more information, see Using a database with a BMP entity bean.
Each ejbPostCreate method must be public, return void, and have the same
arguments as the matching ejbCreate method.
If necessary, both the ejbCreate method and the ejbPostCreate method can throw
the java.rmi.RemoteException exception, the
javax.ejb.CreateException exception, the
javax.ejb.DuplicateKeyException exception, and any user-defined
exceptions.
Figure 44 shows the two ejbCreate methods required by the example
AccountBMBean bean class. No ejbPostCreate methods are required.
As in the AccountBean class, the first ejbCreate method calls the second
ejbCreate method; the latter handles all of the interaction with the data
source. The second method initializes the bean's instance
variables and then ensures that it has a valid connection to the data source
by invoking the checkConnection method. The method then creates,
prepares, and executes an SQL INSERT call on the data source. If the
INSERT call is executed correctly, and only one row is inserted into the data
source, the method returns an object of the bean's primary key
class.
Figure 44. Code example: The ejbCreate methods of the AccountBMBean class
public AccountBMKey ejbCreate(AccountBMKey key) throws CreateException,
RemoteException {
return ejbCreate(key, 1, 0.0f);
}
...
public AccountBMKey ejbCreate(AccountBMKey key, int type, float balance)
throws CreateException, RemoteException
{
accountId = key.accountId;
this.type = type;
this.balance = balance;
checkConnection();
// INSERT into database
try {
String sqlString = "INSERT INTO " + tableName +
" (balance, type, accountid) VALUES (?,?,?)";
PreparedStatement sqlStatement = jdbcConn.prepareStatement(sqlString);
sqlStatement.setFloat(1, balance);
sqlStatement.setInt(2, type);
sqlStatement.setLong(3, accountId);
// Execute query
int updateResults = sqlStatement.executeUpdate();
...
}
catch (Exception e) { // Error occurred during insert
...
}
return key;
}
At a minimum, each entity bean with BMP must define and implement the
ejbFindByPrimaryKey method that takes a primary key and determines if it is
valid and unique for an instance of an enterprise bean; if the primary
key is valid and unique, it returns the primary key. An entity bean can
also define and implement other finder methods to find enterprise bean
instances. All finder methods can throw the
javax.ejb.FinderException exception to indicate an
application-level error. Finder methods designed to find a single bean
can also throw the javax.ejb.ObjectNotFoundException exception,
a subclass of the FinderException class. Finder methods designed to
return multiple beans should not use the ObjectNotFoundException to indicate
that no suitable beans were found; instead, such methods should return
empty return values. Throwing the
java.rmi.RemoteException exception is deprecated; see Standard application exceptions for entity beans for more information.
Like the business methods of the bean class, the ejbFind methods cannot be
invoked directly by the client. Instead, the client invokes a finder
method on the enterprise bean's home interface by using the EJB home
object, and the container invokes the corresponding ejbFind method. The
container invokes an ejbFind method by using a generic instance of that entity
bean in the pooled state.
Because the container uses an instance of an entity bean in the pooled
state to invoke an ejbFind method, the method must do the following:
- Get a connection to the data source (or sources).
- Query the data source for records that match specifications of the finder
method.
- Drop the connection to the data source (or sources).
For more information on these data source tasks, see Using a database with a BMP entity bean.
Figure 45 shows the ejbFindByPrimaryKey method of the example
AccountBMBean class. The ejbFindByPrimaryKey method gets a connection
to its data source by calling the makeConnection method shown in Figure 45. It then creates and invokes an SQL SELECT statement
on the data source by using the specified primary key.
If one and only one record is found, the method returns the primary key
passed to it in the argument. If no records are found or multiple
records are found, the method throws the FinderException. Before
determining whether to return the primary key or throw the FinderException,
the method drops its connection to the data source by calling the
dropConnection method described in Using a database with a BMP entity bean.
Figure 45. Code example: The ejbFindByPrimaryKey method of the AccountBMBean class
public AccountBMKey ejbFindByPrimaryKey (AccountBMKey key)
throws FinderException {
boolean wasFound = false;
boolean foundMultiples = false;
makeConnection();
try {
// SELECT from database
String sqlString = "SELECT balance, type, accountid FROM " + tableName
+ " WHERE accountid = ?";
PreparedStatement sqlStatement = jdbcConn.prepareStatement(sqlString);
long keyValue = key.accountId;
sqlStatement.setLong(1, keyValue);
// Execute query
ResultSet sqlResults = sqlStatement.executeQuery();
// Advance cursor (there should be only one item)
// wasFound will be true if there is one
wasFound = sqlResults.next();
// foundMultiples will be true if more than one is found.
foundMultiples = sqlResults.next();
}
catch (Exception e) { // DB error
...
}
dropConnection();
if (wasFound && !foundMultiples)
{
return key;
}
else
{
// Report finding no key or multiple keys
...
throw(new FinderException(foundStatus));
}
}
Figure 46 shows the ejbFindLargeAccounts method of the example
AccountBMBean class. The ejbFindLargeAccounts method also gets a
connection to its data source by calling the makeConnection method and drops
the connection by using the dropConnection method. The SQL SELECT
statement is also very similar to that used by the ejbFindByPrimaryKey
method. (For more information on these data source tasks and methods,
see Using a database with a BMP entity bean.)
While the ejbFindByPrimaryKey method needs to return only one primary key,
the ejbFindLargeAccounts method can be expected to return zero or more primary
keys in an Enumeration object. To return an enumeration of primary
keys, the ejbFindLargeAccounts method does the following:
- It uses a while loop to examine the result set (sqlResults)
returned by the executeQuery method.
- It inserts each primary key in the result set into a hash table named
resultTable by wrapping the returned account ID in a Long object
and then in an AccountBMKey object. (The Long object,
memberId, is used as the hash table's index.)
- It invokes the elements method on the hash table to obtain the enumeration
of primary keys, which it then returns.
Figure 46. Code example: The ejbFindLargeAccounts method of the AccountBMBean class
public Enumeration ejbFindLargeAccounts(float amount) throws FinderException {
makeConnection();
Enumeration result;
try {
// SELECT from database
String sqlString = "SELECT accountid FROM " + tableName
+ " WHERE balance >= ?";
PreparedStatement sqlStatement = jdbcConn.prepareStatement(sqlString);
sqlStatement.setFloat(1, amount);
// Execute query
ResultSet sqlResults = sqlStatement.executeQuery();
// Set up Hashtable to contain list of primary keys
Hashtable resultTable = new Hashtable();
// Loop through result set until there are no more entries
// Insert each primary key into the resultTable
while(sqlResults.next() == true) {
long acctId = sqlResults.getLong(1);
Long memberId = new Long(acctId);
AccountBMKey key = new AccountBMKey(acctId);
resultTable.put(memberId, key);
}
// Return the resultTable as an Enumeration
result = resultTable.elements();
return result;
} catch (Exception e) {
...
} finally {
dropConnection();
}
}
Each entity bean class must implement the methods inherited from the
javax.ejb.EntityBean interface. The container invokes
these methods to move the bean through different stages in the bean's
life cycle. Unlike an entity bean with CMP, in an entity bean with BMP,
these methods must contain all of the code for the required interaction with
the data source (or sources) used by the bean to store its persistent
data.
- ejbActivate--This method is invoked by the container when the
container selects an entity bean instance from the instance pool and assigns
that instance to a specific existing EJB object. This method must
contain the code required to activate the enterprise bean instance by getting
a connection to the data source and using the bean's
javax.ejb.EntityContext class to obtain the primary key in the
corresponding EJB object.
In the example AccountBMBean class, the ejbActivate method obtains the bean
instance's account ID, sets the value of the accountId
variable, and invokes the checkConnection method to ensure that it has a valid
connection to the data source.
- ejbLoad--This method is invoked by the container to synchronize an
entity bean's persistent variables with the corresponding data in the
data source. (That is, the values of the fields in the data source are
loaded into the persistent variables in the corresponding enterprise bean
instance.) This method must contain the code required to load the
values from the data source and assign those values to the bean's
instance variables.
In the example AccountBMBean class, the ejbLoad method obtains the bean
instance's account ID, sets the value of the accountId
variable, invokes the checkConnection method to ensure that it has a valid
connection to the data source, constructs and executes an SQL SELECT
statement, and sets the values of the type and balance
variables to match the values retrieved from the data source.
- ejbPassivate--This method is invoked by the container to disassociate
an entity bean instance from its EJB object and place the enterprise bean
instance in the instance pool. This method must contain the code
required to "passivate" or deactivate an enterprise bean instance.
Usually, this passivation simply means dropping the connection to the data
source.
In the example AccountBMBean class, the ejbPassivate method invokes the
dropConnection method to drop the connection to the data source.
- ejbRemove--This method is invoked by the container when a client
invokes the remove method inherited by the enterprise bean's home
interface (from the javax.ejb.EJBHome interface) or remote
interface (from the javax.ejb.EJBObject interface). This
method must contain the code required to remove an enterprise bean's
persistent data from the data source. This method can throw the
javax.ejb.RemoveException exception if removal of an enterprise
bean instance is not permitted. Usually, removal involves deleting the
bean instance's data from the data source and then dropping the bean
instance's connection to the data source.
In the example AccountBMBean class, the ejbRemove method invokes the
checkConnection method to ensure that it has a valid connection to the data
source, constructs and executes an SQL DELETE statement, and invokes the
dropConnection method to drop the connection to the data source.
- setEntityContext--This method is invoked by the container to pass a
reference to the javax.ejb.EntityContext interface to an
enterprise bean instance. This method must contain any code required to
store a reference to a context.
In the example AccountBMBean class, the setEntityContext method sets the
value of the entityContext variable to the value passed to it by
the container.
- ejbStore--This method is invoked by the container when the container
needs to synchronize the data in the data source with the values of the
persistent variables in an enterprise bean instance. (That is, the
values of the variables in the enterprise bean instance are copied to the data
source, overwriting the previous values.) This method must contain the
code required to overwrite the data in the data source with the corresponding
values in the enterprise bean instance.
In the example AccountBMBean class, the ejbStore method invokes the
checkConnection method to ensure that it has a valid connection to the data
source and constructs and executes an SQL UPDATE statement.
- unsetEntityContext--This method is invoked by the container, before
an enterprise bean instance is removed, to free up any resources associated
with the enterprise bean instance. This is the last method called prior
to removing an enterprise bean instance.
In the example AccountBMBean class, the unsetEntityContext method sets the
value of the entityContext variable to null.
An entity bean's home interface defines the methods used by EJB clients
to create new instances of the bean, find and remove existing instances, and
obtain metadata about an instance. The home interface is defined by the
enterprise bean developer and implemented in the EJB home class created by the
container during enterprise bean deployment. The container makes the
home interface accessible to clients through the Java Naming and Directory
Interface (JNDI).
By convention, the home interface is named NameHome, where
Name is the name you assign to the enterprise bean. For
example, the AccountBM enterprise bean's home interface is named
AccountBMHome.
Every home interface for an entity bean with BMP must meet the following
requirements:
- It must extend the javax.ejb.EJBHome interface. The
home interface inherits several methods from the
javax.ejb.EJBHome interface. See The javax.ejb.EJBHome interface for information on these
methods.
- Each method in the interface must be either a create method, which
corresponds to an ejbCreate method (and possibly an ejbPostCreate method) in
the enterprise bean class, or a finder method, which corresponds to an ejbFind
method in the enterprise bean class. For more information, see Defining create methods and Defining finder methods.
- The parameters and return value of each method defined in the home
interface must be valid for Java RMI. For more information, see The java.io.Serializable and java.rmi.Remote interfaces. In addition, each method's throws clause must
include the java.rmi.RemoteException exception class.
Figure 47 shows the relevant parts of the definition of the home
interface (AccountBMHome) for the example AccountBM bean. This
interface defines two abstract create methods: the first creates an
AccountBM object by using an associated AccountBMKey object, the second
creates an AccountBM object by using an associated AccountBMKey object and
specifying an account type and an initial balance. The interface
defines the required findByPrimaryKey method and the findLargeAccounts
method.
Figure 47. Code example: The AccountBMHome home interface
...
import java.rmi.*;
import javax.ejb.*;
import java.util.*;
public interface AccountBMHome extends EJBHome {
...
AccountBM create(AccountBMKey key) throws CreateException,
RemoteException;
...
AccountBM create(AccountBMKey key, int type, float amount)
throws CreateException, RemoteException;
...
AccountBM findByPrimaryKey(AccountBMKey key)
throws FinderException, RemoteException;
...
Enumeration findLargeAccounts(float amount)
throws FinderException, RemoteException;
}
A create method is used by a client to create an enterprise bean instance and
insert the data associated with that instance into the data source.
Each create method must be named create and it must have the same number and
types of arguments as a corresponding ejbCreate method in the enterprise bean
class. (The ejbCreate method can itself have a corresponding
ejbPostCreate method.) The return types of the create method and its
corresponding ejbCreate method are always different.
Each create method must meet the following requirements:
- It must be named create.
- It must return the type of the enterprise bean's remote
interface. For example, the return type for the create methods in the
AccountBMHome interface is AccountBM (as shown in Figure 13).
- It must have a throws clause that includes the
java.rmi.RemoteException exception, the
javax.ejb.CreateException exception, and all of the exceptions
defined in the throws clause of the corresponding ejbCreate and ejbPostCreate
methods.
A finder method is used to find one or more existing entity EJB
objects. Each finder method must be named findName, where
Name further describes the finder method's purpose.
At a minimum, each home interface must define the findByPrimaryKey method
that enables a client to locate an EJB object by using the primary key
only. The findByPrimaryKey method has one argument, an object of the
bean's primary key class, and returns the type of the bean's remote
interface.
Every other finder method must meet the following requirements:
- It must return the type of the enterprise bean's remote interface,
the java.util.Enumeration interface, or the
java.util.Collection interface (when a finder method can return
more than one EJB object or an EJB collection).
- It must have a throws clause that includes the
java.rmi.RemoteException and
javax.ejb.FinderException exception classes.
Although every entity bean must contain only the default finder method, you
can write additional ones if needed. For example, the AccountBM
bean's home interface defines the findLargeAccounts method to find
objects that encapsulate accounts with balances of more than a specified
dollar amount, as shown in Figure 47. Because this finder method can be expected to return
a reference to more than one EJB object, its return type is
java.util.Enumeration.
Unlike the implementation in an entity bean with CMP, in an entity bean
with BMP, the bean developer must fully implement the ejbFindByPrimaryKey
method that corresponds to the findByPrimaryKey method. In addition,
the bean developer must write each additional ejbFind method corresponding to
the finder methods defined in the home interface. The implementation of
the ejbFind methods in the AccountBMBean class is discussed in Implementing the ejbFindByPrimaryKey and other ejbFind methods.
An entity bean's remote interface provides access to the business methods
available in the bean class. It also provides methods to remove an EJB
object associated with a bean instance and to obtain the bean instance's
home interface, object handle, and primary key. The remote interface is
defined by the EJB developer and implemented in the EJB object class created
by the container during enterprise bean deployment.
By convention, the remote interface is named Name, where
Name is the name you assign to the enterprise bean. For
example, the AccountBM enterprise bean's remote interface is named
AccountBM.
Every remote interface must meet the following requirements:
- It must extend the javax.ejb.EJBObject interface. The
remote interface inherits several methods from the
javax.ejb.EJBObject interface. See Methods inherited from javax.ejb.EJBObject for information on these
methods.
- It must define a corresponding business method for every business method
implemented in the enterprise bean class.
- The parameters and return value of each method defined in the interface
must be valid for Java RMI. For more information, see The java.io.Serializable and java.rmi.Remote interfaces.
- Each method's throws clause must include the
java.rmi.RemoteException exception class.
Figure 48 shows the relevant parts of the definition of the remote
interface (AccountBM) for the example AccountBM enterprise bean. This
interface defines four methods for displaying and manipulating the account
balance that exactly match the business methods implemented in the
AccountBMBean class.
All of the business methods throw the java.rmi.RemoteException
exception class. In addition, the subtract method must throw the
user-defined exception
com.ibm.ejs.doc.account.InsufficientFundsException
because the corresponding method in the bean class throws this
exception. Furthermore, any client that calls this method must either
handle the exception or pass it on by throwing it.
Figure 48. Code example: The AccountBM remote interface
...
import java.rmi.*;
import javax.ejb.*;
import com.ibm.ejs.doc.account.InsufficientFundsException;
public interface AccountBM extends EJBObject {
...
float add(float amount) throws RemoteException;
...
float getBalance() throws RemoteException;
...
void setBalance(float amount) throws RemoteException;
...
float subtract(float amount) throws InsufficientFundsException,
RemoteException;
}
Every entity EJB object has a unique identity within a container that is
defined by a combination of the object's home interface name and its
primary key, the latter of which is assigned to the object at creation.
If two EJB objects have the same identity, they are considered
identical.
The primary key class is used to encapsulate an EJB object's primary
key. In an entity bean (with BMP or CMP), you can write a distinct
primary key class or you can use an existing class as the primary key class,
as long as that class is serializable. For more information, see The java.io.Serializable and java.rmi.Remote interfaces.
The example AccountBM bean uses a primary key class that is identical to
the AccountKey class contained in the Account bean shown in Figure 16, with the exception that the key class is named
AccountBMKey.
- Note:
- The primary key class of an entity bean with BMP must implement the hashCode
and equals method. In addition, the variables that make up the primary
key must be public.
The java.lang.Long class is also a good candidate for a
primary key class for the AccountBM bean.
In an entity bean with BMP, each ejbFind method and all of the life cycle
methods (ejbActivate, ejbCreate, ejbLoad, ejbPassivate, and ejbStore) must
interact with the data source (or sources) used by the bean to maintain its
persistent data. To interact with a supported database, the BMP entity
bean must contain the code to manage database connections and to manipulate
the data in the database.
The EJB server uses a set of specialized beans to encapsulate information
about databases and an IBM-specific interface to JDBC to allow entity bean
interaction with a connection manager. For more information, see Managing database connections in the EJB server environment
In general, there are three approaches to getting and releasing connections
to databases:
- The bean can get a database connection in the setEntityContext method and
release it in the unsetEntityContext method. This approach is the
easiest for the enterprise bean developer to implement. However,
without a connection manager, this approach is not viable because under it
bean instances hold onto database connections even when they are not in use
(that is, when the bean instance is passivated). Even with a connection
manager, this approach does not scale well.
- The bean can get a database connection in the ejbActivate and ejbCreate
methods, get and release a database connection in each ejbFind method, and
release the database connection in the ejbPassivate and ejbRemove
methods. This approach is somewhat more difficult to implement, but it
ensures that only those bean instances that are activated have connections to
the database.
- The bean can get and release a database connection in each method that
requires a connection: ejbActivate, ejbCreate, ejbFind, ejbLoad, and
ejbStore. This approach is more difficult to implement than the first
approach, but is no more difficult than the second approach. This
approach is the most efficient in terms of connection use and also the most
scalable.
The example AccountBM bean, uses the second approach described in the
preceding text. The AccountBMBean class contains two methods for making
a connection to the DB2 database, checkConnection and makeConnection, and one
method to drop connections: dropConnection.The code required to
make the AccountBM bean work with the connection manager is shown in Managing database connections in the EJB server environment
The code required to manipulate data in a database is described in Manipulating data in a database.
In the EJB server environment, the administrator creates a specialized set of
entity beans that encapsulate information about the database and the database
driver. These specialized entity beans are created by using the
WebSphere Administrative Console.
An entity bean that requires access to a database must use JNDI to create a
reference to an EJB object associated with the right database bean
instance. The entity bean can then use the IBM-specific interface
(named
com.ibm.db2.jdbc.app.stdext.javax.sql.DataSource)
to get and release connections to the database.
The DataSource interface enables the entity bean to transparently interact
with the connection manager of the EJB server. The connection manager
creates a pool of database connections, which are allocated and deallocated to
individual entity beans as needed.
Before a BMP entity bean can get a connection to a database, the entity bean
must perform a JNDI lookup on the data source entity bean associated with the
database used to store the BMP entity bean's persistent data. Figure 49 shows the code required to create an InitialContext object
and then get an EJB object reference to a database bean instance. The
JNDI name of the database bean is defined by the administrator; it is
recommended that the JNDI naming convention be followed when defining this
name. The value of the required database-specific variables are
obtained by the getEnvProps method, which accesses the corresponding
environment variables from the deployed enterprise bean.
Because the connection manager creates and drops the actual database
connections and simply allocates and deallocates these connections as
required, there is no need for the BMP entity bean to load and register the
database driver. Therefore, there is no need to define the
driverName and jdbcUrl variables discussed in Defining instance variables.
Figure 49. Code example: Getting an EJB object reference to a data source bean instance in the setEntityContext method (rewritten to use DataSource)
...
# import com.ibm.db2.jdbc.app.stdext.javax.sql.DataSource;
# import javax.naming.*;
...
InitialContext initContext = null;
DataSource ds = null;
...
public void setEntityContext(EntityContext ctx)
throws EJBException {
entityContext = ctx;
try {
getEnvProps();
ds = initContext.lookup("jdbc/sample");
} catch (NamingException e) {
...
}
}
...
After creating an EJB object reference for the appropriate database bean
instance, that object reference is used to get and release connections to the
corresponding database. Unlike when using the DriverManager interface,
when using the DataSource interface, the BMP entity bean does not really
create and close data connections; instead, the connection manager
allocates and deallocates connections as required by the entity bean.
Nevertheless, a BMP entity bean must still contain code to send allocation and
deallocation requests to the connection manager.
In the AccountBMBean class, the checkConnection method is called within
other bean class methods that require a database connection, but for which it
can be assumed that a connection already exists. This method checks to
make sure that the connection is still available by checking if the
jdbcConn variable is set to null. If the variable is null,
the makeConnection method is invoked to get the connection (that is a
connection allocation request is sent to the connection manager).
The makeConnection method is invoked when a database connection is
required. It invokes the getConnection method on the data source
object. The getConnection method is overloaded: it can take
either a user ID and password or no arguments, in which case the user ID and
password are implicitly set to null (this version is used in Figure 50).
Figure 50. Code example: The checkConnection and makeConnection methods of the AccountBMBean class (rewritten to use DataSource)
private void checkConnection() throws EJBeException {
if (jdbcConn == null) {
makeConnection();
}
return;
}
...
private void makeConnection() throws EJBeException {
...
try {
// Open database connection
jdbcConn = ds.getConnection();
} catch(Exception e) { // Could not get database connection
...
}
}
Entity beans with BMP must also release database connections when a particular
bean instance no longer requires it (that is, they must send a deallocation
request to the connection manager). The AccountBMBean class contains a
dropConnection method to handle this task. To release the database
connection, the dropConnection method does the following (as shown in Figure 51):
- Invokes the close method on the connection object to tell the connection
manager that the connection is no longer needed.
- Sets the connection object reference to null.
Putting the close method inside a try/catch/finally block ensures that the
connection object reference is always set to null even if the close method
fails for some reason. Nothing is done in the catch block because the
connection manager must clean up idle connections; this is not the job of
the enterprise bean code.
Figure 51. Code example: The dropConnection method of the AccountBMBean class (rewritten to use DataSource)
private void dropConnection() {
try {
// Close the connection
jdbcConn.close();
catch (SQLException ex) {
// Do nothing
} finally {
jdbcConn = null;
}
}
After an instance of a BMP entity bean obtains a connection to its database,
it can read and write data. The AccountBMBean class communicates with
the DB2 database by constructing and executing Java Structured Query Language
(JSQL) calls by using the java.sql.PreparedStatement
interface.
As shown in Figure 52, the SQL call is created as a String
(sqlString). The String variable is passed to the
java.sql.Connection.prepareStatement method; and the
values of each variable in the SQL call are set by using the various setter
methods of the PreparedStatement class. (The variables are substituted
for the question marks in the sqlString variable.) Invoking
the PreparedStatement.executeUpdate method executes the SQL
call.
Figure 52. Code example: Constructing and executing an SQL update call in an ejbCreate method
private void ejbCreate(AccountBMKey key, int type, float initialBalance)
throws CreateException, EJBException {
// Initialize persistent variables and check for good DB connection
...
// INSERT into database
try {
String sqlString = "INSERT INTO " + tableName +
" (balance, type, accountid) VALUES (?,?,?)";
PreparedStatement sqlStatement = jdbcConn.prepareStatement(sqlString);
sqlStatement.setFloat(1, balance);
sqlStatement.setInt(2, type);
sqlStatement.setLong(3, accountId);
// Execute query
int updateResults = sqlStatement.executeUpdate();
...
}
catch (Exception e) { // Error occurred during insert
...
}
...
}
The executeUpdate method is called to insert or update data in a
database; the executeQuery method is called to get data from a
database. When data is retrieved from a database, the executeQuery
method returns a java.sql.ResultSet object, which must be
examined and manipulated using the methods of that class.
- Note:
- To improve scalability and performance, you do not need to call
PreparedStatement for each database update. Instead, you can cache the
results of the first PreparedStatement call.
Figure 53 provides an example of how the data in a ResultSet is
manipulated in the ejbLoad method of the AccountBMBean class.
Figure 53. Code example: Manipulating a ResultSet object in the ejbLoad method
public void ejbLoad () throws EJBeException {
// Get data from database
try {
// SELECT from database
...
// Execute query
ResultSet sqlResults = sqlStatement.executeQuery();
// Advance cursor (there should be only one item)
sqlResults.next();
// Pull out results
balance = sqlResults.getFloat(1);
type = sqlResults.getInt(2);
} catch (Exception e) {
// Something happened while loading data.
...
}
}
In most situations, an enterprise bean can depend on the container to manage
transactions within the bean. In these situations, all you need to do
is set the appropriate transactional properties in the deployment descriptor
as described in Enabling transactions and security in enterprise beans.
Under certain circumstances, however, it can be necessary to have an
enterprise bean participate directly in transactions. By setting the
transaction attribute in an enterprise bean's deployment
descriptor to BeanManaged, you indicate to the container that the bean is an
active participant in transactions.
- Note:
- The value BeanManaged is not a valid value for the transaction
deployment descriptor attribute in entity beans. In other words, entity
beans cannot manage transactions.
When writing the code required by an enterprise bean to manage its own
transactions, remember the following basic rules:
- An instance of a stateless session bean cannot reuse the same
transaction context across multiple methods called by an EJB client.
Therefore, it is recommended that the transaction context be a local variable
to each method that requires a transaction context.
- An instance of a stateful session bean can reuse the same transaction
context across multiple methods called by an EJB client. Therefore,
make the transaction context an instance variable or a local method variable
at your discretion. (When a transaction spans multiple methods, you can
use the javax.ejb.SessionSynchronization interface to
synchronize the conversational state with the transaction.)
Figure 54 shows the standard code required to obtain an object
encapsulating the transaction context. There are three basics steps
involved:
- The enterprise bean class must set the value of the
javax.ejb.SessionContext object reference in the
setSessionContext method.
- A javax.transaction.UserTransaction object is created by
calling the getUserTransaction method on the SessionContext object
reference.
- The UserTransaction object is used to participate in the transaction by
calling transaction methods such as begin and commit as needed. If a
enterprise bean begins a transaction, it must also complete that transaction
either by invoking the commit method or the rollback method.
- Note:
- In both EJB servers, the getUserTransaction method of the
javax.ejb.EJBContext interface (which is inherited by the
SessionContext interface) returns an object of type
javax.transaction.UserTransaction rather than type
javax.jts.UserTransaction. While this is a deviation from
the 1.0 version of the EJB Specification, the 1.1 version of the
EJB Specification requires that the getUserTransaction method return an object
of type javax.transaction.UserTransaction and drops the
requirement to return objects of type
javax.jts.UserTransaction.
Figure 54. Code example: Getting an object that encapsulates a transaction context
...
import javax.transaction.*;
...
public class MyStatelessSessionBean implements SessionBean {
private SessionContext mySessionCtx = null;
...
public void setSessionContext(.SessionContext ctx) throws EJBException {
mySessionCtx = ctx;
}
...
public float doSomething(long arg1) throws FinderException, EJBException {
UserTransaction userTran = mySessionCtx.getUserTransaction();
...
// User userTran object to call transaction methods
userTran.begin();
// Do transactional work
...
userTran.commit();
...
}
...
}
The following methods are available with the UserTransaction
interface:
- begin--Begins a transaction. This method takes no arguments
and returns void.
- commit--Attempts to commit a transaction; assuming that nothing
causes the transaction to be rolled back, successful completion of this method
commits the transaction. This method takes no arguments and returns
void.
- getStatus--Returns the status of the referenced transaction.
This method takes no arguments and returns int; if no transaction is
associated with the reference, STATUS_NO_TRANSACTION is returned. The
following are the valid return values for this method:
- STATUS_ACTIVE--Indicates that transaction processing is still in
progress.
- STATUS_COMMITTED--Indicates that a transaction has been committed and
the effects of the transaction have been made permanent.
- STATUS_COMMITTING--Indicates that a transaction is in the process of
committing (that is, the transaction has started committing but has not
completed the process).
- STATUS_MARKED_ROLLBACK--Indicates that a transaction is marked to be
rolled back.
- STATUS_NO_TRANSACTION--Indicates that a transaction does not exist in
the current transaction context.
- STATUS_PREPARED--Indicates that a transaction has been prepared but
not completed.
- STATUS_PREPARING--Indicates that a transaction is in the process of
preparing (that is, the transaction has started preparing but has not
completed the process).
- STATUS_ROLLEDBACK--Indicates that a transaction has been rolled
back.
- STATUS_ROLLING_BACK--Indicates that a transaction is in the process
of rolling back (that is, the transaction has started rolling back but has not
completed the process).
- STATUS_UNKNOWN--Indicates that the status of a transaction is
unknown.
- rollback--Rolls back the referenced transaction. This method
takes no arguments and returns void.
- setRollbackOnly--Specifies that the only possible outcome of the
transaction is rollback. This method takes no arguments and returns
void.
- setTransactionTimeout--Sets the timeout (in seconds) associated with
the transaction. If some transaction participant has not specifically
set this value, a default timeout is used. This method takes a number
of seconds (as type int) and returns void.