To implement BMP, you must code the callback methods within the bean class to perform the required database access. When a client calls a create method on the home interface, the container calls the corresponding ejbCreate on an instance of your bean class. The instance on which the method is called is the instance the container has selected from the pool to associate with the new entity object that's being created. Your ejbCreate method is responsible for initializing the attributes of the entity, inserting it into the database, and returning its primary key value. Remember from Chapter 5 that the signature of an ejbCreate method must have the same parameter list as the corresponding create method in the home interface, and its return type must be the primary key class. Listing 6.3 shows an example of the method you could implement for the createWithData method declared in EnglishAuctionHome . As shown here, the bean class is declared to extend the AbstractEntity class introduced in Chapter 5. This class is an adapter for the EntityBean interface. Because EnglishAuctionBean extends AbstractEntity , it isn't necessary to declare this class to implement EntityBean . However, some development tools won't recognize a class as an enterprise bean if it doesn't explicitly extend the corresponding interface. Listing 6.3 ejbCreateWithData “A BMP ejbCreate Method Inserts an Entity into the Databasepackage com.que.ejb20.auction.model; ... public class EnglishAuctionBean extends AbstractEntity implements EntityBean { ... public Integer ejbCreateWithData(String name, String description) throws CreateException { // throw an application exception if the name isn't valid if ((name == null) (name.trim().length() == 0)) { throw new CreateException("Cannot create an auction without a name"); } Connection con = null; PreparedStatement stmt = null; try { // assign the primary key and the initialization parameters setId(computeNextPK("auctionseq")); setName(name); setDescription(description); // default to Pending status status = IAuctionStatus.AUCTION_PENDING; con = BMPHelper.getConnection("auctionSource"); // build a prepared statement and insert the new entity into the // database (let the other attributes default to null) stmt = con.prepareStatement( "INSERT INTO auction (id, Name, Description, Status) VALUES (?,?,?,?)"); stmt.setInt(1, id.intValue()); stmt.setString(2, name); stmt.setString(3, description); stmt.setString(4, status); // perform the insert and throw an exception if it fails int rowsInserted = stmt.executeUpdate(); if (rowsInserted != 1) { throw new EJBException( "Could not insert the auction into the database"); } // everything worked, return the primary key return getId(); } catch (SQLException e) { // throw a system exception if a database access error occurs throw new EJBException; } finally { // close the connection BMPHelper.cleanup(stmt, con); } } ... } The ejbCreateWithData method starts by performing any data validation that's required and then assigning its parameter values to the bean instance. You should report errors related to initialization data using a CreateException . You'll see more about exceptions such as CreateException in Chapter 13, "Exception Handling." The getConnection method of BMPHelper from Listing 6.1 provides a Connection object to ejbCreateWithData that's used to build an insert statement. After the insert statement is executed, the method returns the primary key assigned to the entity object. The cleanup method of BMPHelper called from the finally block is a simple utility method used to encapsulate the checks and exception handling needed by all the callback methods to close a statement and database connection before returning. This method appears in Listing 6.4. Because the application server is managing the pooling of connections for you, you're not really closing the connection by calling the close method. Calling this method lets the container know that you're finished with the connection so that it can be returned to the pool. It's important to release a limited resource such as this, so the call to the close method (or the cleanup method in this example) should always be placed in a finally block. Listing 6.4 cleanup “Common Behavior for Closing a Statement and a Connectionpackage com.que.ejb20.common.ejb; ... public class BMPHelper { ... public static void cleanup(Statement stmt, Connection con) { try { if (stmt != null) { stmt.close(); } if (con != null) { con.close(); } } catch (SQLException e) { throw new EJBException; } } ... }
Notice that ejbCreateWithData calls a computeNextPK method to assign its primary key value. Each table in the auction database uses an integer primary key. Using a unique number or string that has nothing to do with the data for a primary key value is a common approach. Just as common is the accompanying problem of deciding how to generate these unique values when rows are inserted. Most databases provide the capability to automatically generate a unique sequence number when a row is inserted. Using these native sequence numbers removes the problem of key generation but it can lead to portability problems. Even though most databases support this feature, not all of them do, and the implementations differ in how an application retrieves a newly generated key from the database. Another approach is to use a routine that merges information, such as the IP address or some other unique property of the server, with the current time and date to produce a unique string. This approach is reliable but it has poor performance compared to approaches that don't depend on string manipulation. A fairly simple alternative to the two mentioned so far is to implement your own sequence table in the database for each table that requires an integer primary key. A sequence table needs to hold only a single value to represent the last primary key value assigned to the table with which it's associated. This approach was selected for the auction example. Listing 6.5 shows how you can generate sequence numbers using a simple sequence table. Listing 6.5 computeNextPK “A Method for Generating a Primary Key Value Using a Sequence Tablepackage com.que.ejb20.auction.model; ... public class EnglishAuctionBean extends AbstractEntity implements EntityBean { ... protected Integer computeNextPK(String tableName) throws SQLException { Connection con = null; PreparedStatement stmt = null; ResultSet rs = null; try { con = BMPHelper.getConnection("auctionSource"); // update the sequence value in the database stmt = con.prepareStatement("UPDATE " + tableName + " set next_id = next_id + 1"); if (stmt.executeUpdate() != 1) { throw new SQLException("Error generating primary key"); } stmt.close(); // retrieve the sequence value and use it as the primary key stmt = con.prepareStatement("SELECT next_id from " + tableName); rs = stmt.executeQuery(); boolean found = rs.next(); if (found) { return new Integer(rs.getInt("next_id")); } else { throw new SQLException("Error generating primary key"); } } finally { // close the connection BMPHelper.cleanup(stmt, con); } } ... } The sequence tables accessed by computeNextPK can be created using the following SQL: CREATE TABLE auctionseq ( next_id int NOT NULL ); insert into auctionseq (next_id) values (1); CREATE TABLE bidseq ( next_id int NOT NULL ); insert into bidseq (next_id) values (1); Besides the source shown so far, ejbCreateWithData also references the IAuctionStatus interface. This interface, which appears in Listing 6.6, simply defines the strings used in the database to represent the auction state. Listing 6.6 IAuctionStatus.java “A Declaration of the Strings Used to Report Auction Statepackage com.que.ejb20.auction.model; /** * Title: IAuctionStatus<p> * Description: Constants that define the allowed auction states<p> */ public interface IAuctionStatus { public static final String AUCTION_PENDING = "Pending"; public static final String AUCTION_OPEN = "Open"; public static final String AUCTION_CANCELLED = "Cancelled"; public static final String AUCTION_CLOSED = "Closed"; } Each ejbCreate method must have a matching ejbPostCreate method that is declared with the same parameter list but with a return type of void . The container calls this method after the ejbCreate method completes. Unlike ejbCreate , your instance is associated with an entity object identity within an ejbPostCreate method. This means that you can access the methods of the instance's EntityContext to get the primary key or the EJBObject . What this offers you is the capability to define associations between the entity and other objects. If, as part of creating an entity object, you need to insert a row into another table that has a foreign key back to your entity, ejbPostCreate is the place to do it. In the case of the auction entity, its associations to its bids and the item it offers aren't established at creation, so there's nothing to do at this point. EnglishAuctionBean only needs a do-nothing implementation of this method as shown in the following: public void ejbPostCreateWithData(String name, String description) throws CreateException { // nothing to do } |