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.


Developing entity beans with BMP

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:

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).

Writing the enterprise bean 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:

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 55 shows the import statements and class declaration for the example AccountBM enterprise bean.

Figure 55. 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 {
   ...
}

Defining instance variables
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:

The AccountBMBean class contains several nonpersistent instance variables including the following:

Figure 56. 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 connections in the EJB server (CB) environment or Managing database connections in the EJB server (AE) environment.

For more information on how to set an enterprise bean's environment variables, refer to Setting environment variables for an enterprise bean.

Although Figure 56 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.

Implementing the business methods
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 20.

Implementing the ejbCreate and ejbPostCreate methods
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:

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 57 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 57. 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;
}

Implementing the ejbFindByPrimaryKey and other ejbFind methods
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:

  1. Get a connection to the data source (or sources).

  2. Query the data source for records that match specifications of the finder method.

  3. 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 58 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 58. 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 58. 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 59 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:

  1. It uses a while loop to examine the result set (sqlResults) returned by the executeQuery method.

  2. 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.)

  3. It invokes the elements method on the hash table to obtain the enumeration of primary keys, which it then returns.

Figure 59. 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();
     }
}

Implementing the EntityBean interface
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.

Writing the home interface (entity with BMP)
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:

Figure 60 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 60. 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;
}

Defining create methods
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:

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

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 60. 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.

Writing the remote interface (entity with BMP)
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:

Figure 61 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 61. 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;
}

Writing or selecting the primary key class (entity with BMP)
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 26, with the exception that the key class is named AccountBMKey.

Note:
For the EJB server (AE) environment, 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.


Using a database with a BMP entity 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 code required to manage database connections varies across the EJB server implementations:

In general, there are three approaches to getting and releasing connections to databases:

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. These methods must be coded differently based on which EJB server environment you use:

The code required to manipulate data in a database is identical for both EJB server environments. For more information, see Manipulating data in a database.

Managing connections in the EJB server (CB) environment
In the EJB server (CB) environment, both JDBC 1.0 connectivity (using the java.sql.DriverManager interface) and JDBC 2.0 connectivity (using the javax.sql.DataSource interface) are supported, although full JDBC 2.0 support requires DB2 version 7.1, FixPack 2.

Under JDBC 2.0, database connections are made as described in Managing database connections in the EJB server (AE) environment. You must replace the Advanced Edition-specific com.ibm.db2.jdbc.app.stdext.javax.sql.DataSource interface with the standard JDBC 2.0 interface javax.sql.DataSource interface. (When you are using DB2 7.1, FixPack 2, this is implemented by the COM.ibm.db2.jdbc.DB2DataSource class, which an administrator must bind into the JNDI namespace.)

Under JDBC 1.0, the java.sql.DriverManager interface is used to load and register a database driver and to get and release connections to the database. This process is described in the rest of this section.

Loading and registering a data source
The example AccountBM bean uses an IBM DB2 relational database to store its persistent data. To interact with DB2, the example bean must load one of the available JDBC drivers. Figure 62 shows the code required to load the driver class. The value of the driverName variable is obtained by the getEnvProps method, which accesses a corresponding environment variable in the deployed enterprise bean.

The Class.forName method loads and registers the driver class. The AccountBM bean loads the driver in its setEntityContext method, ensuring that every instance of the bean has immediate access to the driver after creating the bean instance and establishing the bean's context.

Note:
In the EJB server (CB) environment, entity beans with BMP that use JDBC to access a database cannot participate in distributed transactions because the environment does not support XA-enabled JDBC.

Figure 62. Code example: Loading and registering a JDBC driver in the setEntityContext method


public void setEntityContext(EntityContext ctx)
     throws EJBException {
     entityContext = ctx;
     try {
          getEnvProps();
          // Load the applet driver for DB2
          Class.forName(driverName);
     } catch (Exception e) {
          ...
     }
}

Creating and closing a connection to a database
After loading and registering a database driver, the BMP entity bean must get a connection to the database. When it no longer needs that connection, the BMP entity bean must close the connection.

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.

The makeConnection method is invoked when a new database connection is required. It invokes the static method java.sql.DriverManager.getConnection and passes the DB2 URL value defined in the jdbcUrl variable (and described in Defining instance variables). The getConnection method is overloaded; the method shown here only uses the database URL, other versions require the URL and the database user ID or the URL, database user ID, and the user password.

Figure 63. Code example: The checkConnection and makeConnection methods of the AccountBMBean class


# import java.sql.*;
...
private void checkConnection() throws EJBException {
     if (jdbcConn == null) {
          makeConnection();
     }
     return;
}
...
private void makeConnection() throws EJBException {
     ...
     try {
          // Open database connection
          jdbcConn = DriverManager.getConnection(jdbcUrl);
     } catch(Exception e) { // Could not get database connection
          ...
     }
}
Entity beans with BMP must also drop database connections when a particular bean instance no longer requires it. The AccountBMBean class contains a dropConnection method to handle this task. To drop the database connection, the dropConnection method does the following:

  1. Invokes the commit method on the connection object (jdbcConn), to drop any locks held on the database.

  2. Invokes the close method on the connection object to close the connection.

  3. Sets the connection object reference to null.

Figure 64. Code example: The dropConnection method of the AccountBMBean class


private void dropConnection() {
     try {
          // Close and delete jdbcConn
          jdbcConn.commit();
     } catch (Exception e) { 
          // Could not commit transactions to database
          ...
     } finally {
          jdbcConn.close();
          jdbcConn = null;
     }
}

Managing database connections in the EJB server (AE) environment
In the EJB server (AE) 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 (AE). The connection manager creates a pool of database connections, which are allocated and deallocated to individual entity beans as needed.

Note:
The example code contained in this section cannot be found in the AccountBMBean, which manages database connections by using the DriverManager interface described in Managing connections in the EJB server (CB) environment. This section shows the code that is required if the AccountBM bean were rewritten to use the DataSource interface.

Getting an EJB object reference to a data source bean instance
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 65 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 65. 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) {
               ...
          }
     }
...

Allocating and deallocating a connection to a database
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 66).

Figure 66. 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 67):

  1. Invokes the close method on the connection object to tell the connection manager that the connection is no longer needed.

  2. 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 67. 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;
     }
}

Manipulating data in a database
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 68, 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 68. 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 69 provides an example of how the data in a ResultSet is manipulated in the ejbLoad method of the AccountBMBean class.

Figure 69. 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.
          ...
     }
}

Using bean-managed transactions

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 TX_BEAN_MANAGED, you indicate to the container that the bean is an active participant in transactions.

Note:
The value TX_BEAN_MANAGED 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:
Note:
In the EJB server (CB) environment, a stateful session bean that implements the TX_BEAN_MANAGED attribute must begin and complete a transaction within the scope of a single method.
Figure 70 shows the standard code required to obtain an object encapsulating the transaction context. There are three basics steps involved:

  1. The enterprise bean class must set the value of the javax.ejb.SessionContext object reference in the setSessionContext method.

  2. A javax.transaction.UserTransaction object is created by calling the getUserTransaction method on the SessionContext object reference.

  3. 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 70. 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: