A Bean-Managed Persistent Entity Bean

As mentioned previously, an entity bean differs from a session bean in that an entity bean is persistent meaning that, conceptually, once an entity bean is created, it continues to exist, and until we explicitly destroy it we can use it and make changes to it, even if the client application that creates it closes or crashes or the EJB container it lives in closes or crashes (once the container is re-started, of course). A Web application, for example, might use an entity bean to store customer orders. If we create an order one day, we can go back the next day or week later, check on its status, and possibly make changes or cancel it.

Of course, an entity bean doesn't really have a continuous existence as a Java object in a virtual reality manifested by the EJB container this is an illusion it attempts to create. Behind the scenes, the EJB container carefully manages the entity bean's data typically by using a database and storing, updating, and deleting the data as required. Another way to look at it is to consider the entity bean to be the set of data associated with a primary key, regardless of whether the data is stored in a database as a database record, in memory as a Java object, or on disk as a serialized object. It is the job of the EJB container to move the data around as appropriate and to keep these different forms properly synchronized in response to client applications.

We don't have to worry about any of the details in the case of a container-managed persistent bean (except when we first create the EJB and map the bean to a database table and its columns by either using the JDeveloper wizard or by writing the deployment descriptor). The EJB container and its deployment tools take care of creating the database code and calling the appropriate methods to create, find, change, or destroy the CMP entity bean.

In the case of a bean-managed persistent entity bean, the EJB container will automatically make the appropriate method calls in response to requests to create, find, change, or destroy a BMP bean, but we are responsible for writing the actual code in these methods to perform the database operations.

In order for the EJB container to perform its database-related duties, we need to provide an implementation for the following methods in the bean implementation file:

  • ejbCreate()

  • ejbFindByPrimaryKey()

  • ejbActivate()

  • ejbLoad()

  • ejbStore()

  • ejbPassivate()

  • ejbRemove()

  • setEntityContext()

  • unsetEntityContext()

Database operations in these methods can be implemented in a number of different ways, using SQL/J, JDBC, Java Data Objects, or another object-relational mapping tool. In the example that follows, we'll use the most usual method, using JDBC.

Creating the BMP Entity Bean Example

To keep this example simple, we'll create a BMP for managing a small set of user information. First, using SQL*Plus, create a new table in the database.

 CREATE TABLE USER_INFO(   USERNAME  VARCHAR2(32),   EMAIL VARCHAR2(32),   PASSWORD VARCHAR2(32),   MEMBER_SINCE DATE,   PRIMARY KEY (EMAIL) ); 

Next, using JDeveloper, following the same general steps described above, create a new empty project, UserInfo, in the ejbExamples workspace, and in that project, create a BMP EJB named UserInfo. The EJB wizard at one point will allow you to define or select a primary key: Click on the selection Built-in JDK class. By default, this class should already be java.lang.String; if it's not, select this from the drop-down list, then press Finish.

JDeveloper will create a number of files. These include:

  • Bean implementation: UserInfoBean

  • Home interface: UserInfoHome

  • Remote interface: UserInfo

  • Deployment descriptor: ejb-jar.xml

Our job of turning these files into a fully functional BMP will consist of the following tasks:

  • In Bean implementation, UserInfoBean:

    • Add private instance variables for the data.

    • Modify or add ejbCreate() and ejbPostCreate() methods, as necessary.

    • Implement the remaining required ejbXXX methods.

    • Add additional finder methods, if desired.

    • Create getter/setter methods to allow client applications to retrieve information from the EJB.

  • In home interface, UserInfoHome:

    • Modify or add create() methods to the home interface to the home.

    • Add corresponding finder methods to the home interface, if necessary.

  • In remote interface, UserInfo:

    • Add abstract getter/setter methods to the remote interface corresponding to methods added to the bean implementation so that the client application can use them.

The Bean Implementation UserInfoBean

This is the start of UserInfoBean, with the imports and instance variables required by the rest of the methods:

 package mypackage4.impl; import javax.ejb.EntityBean; import javax.ejb.EntityContext; import javax.ejb.FinderException; import javax.ejb.CreateException; import java.rmi.RemoteException; import javax.naming.InitialContext; import java.sql.*; public class UserInfoBean implements EntityBean {   public EntityContext entityContext;   String email;   String username;   String password;   java.util.Date memberSince;   Connection conn; 

Note that, in addition to variables corresponding to the bean's persistent attributes, there are variables for the EJB context and for a database connection. The context is necessary because we'll need it later to obtain the bean's primary key. We keep the database connection in an instance variable so we can better manage and reduce the number of times we need to open and close the connection to the database.

ejbCreate() and ejbPostCreate()

These two methods are called one after the other by the EJB container, in this order, when we call the home interface's create() method. It's possible to have multiple sets of these methods. The methods in each set ejbCreate(), ejbPostCreate(), and the home interface's create() must have matching parameter lists.

Depending on the application's logic, we might initially create a bean with only a primary key, then add additional information later, in which case, each method would have only a single parameter: the primary key.

Here, we'll provide only one way to create the bean, requiring that the client application provide email address, username, and password. We'll automatically initialize the memberSince attribute to the current date.

ejbCreate() and ejbPostCreate() must, between them, set the bean's instance variables and use an SQL INSERT to create a new corresponding record in the database. Some prefer to do all of this in the ejbCreate() class and leave ejbPostCreate() empty, but here we split the tasks between the two methods:

 public String ejbCreate(String email,       String username, String password)   {     System.out.println("--- ejbCreate ---");     this.email = email;     this.username = username;     this.password = password;     this.memberSince = new java.sql.Date                        ((new java.util.Date()).getTime());     return email;   }   public void ejbPostCreate(String email,       String username, String password)   throws CreateException   {     System.out.println("--- ejbPostCreate ---");     getConnection();     try     {       String sql = "INSERT INTO USER_INFO " +                    "(EMAIL, USERNAME, PASSWORD, MEMBER_SINCE) " +                    "VALUES(?,?,?,?)";       PreparedStatement ps = conn.prepareStatement(sql);       ps.setString(1, email);       ps.setString(2, username);       ps.setString(3, password);       ps.setDate(4, new java.sql.Date(memberSince.getTime()));       ps.executeUpdate();       ps.close();     }     catch(SQLException e)     {         throw new CreateException("Unable to create new record.");     }    } 

Notice that, to perform SQL operations with JDBC, we first obtain a database connection. We leave the connection open here because it is a high-overhead operation and one that some of the other methods will need to use it soon.

EjbFindByPrimaryKey()

This is the single finder method that we are required to implement. It has one parameter, the primary key. The only real work that our implementation of this method needs to do is verify that a record with the specified key exists in the database. If the key is not found, it should throw a FinderException; otherwise, it merely returns the same primary key that was passed in as a parameter.

 public String ejbFindByPrimaryKey(String email) throws FinderException {   System.out.println("--- ejbFindByPrimaryKey ---");   if (email == null || email.equals(""))   {     throw new FinderException("Primary key cannot be null");   }   boolean success = false;   try   {     String sql = "SELECT EMAIL FROM USER_INFO " +                  "WHERE EMAIL = ?";     getConnection();     PreparedStatement ps = conn.prepareStatement(sql);     ps.setString(1, email);     ps.executeQuery();     ResultSet rs = ps.getResultSet();     success = rs.next();     ps.close();   }   catch(SQLException e)   {     System.out.println("ejbFindByPrimaryKey() caught: " + e);   }   if(!success)   {     disconnect();     throw new FinderException("PK email not found");   }   return email; } 

Notice that whether this returns the primary key or throws an exception depends on the variable success which is obtained by calling the ResultSet's next() method; we don't use the ResultSet apart from this simple check.

You may be wondering how the record is read into the bean's instance variable. The answer is that, once we return the primary key to the EJB container, assuring it that the record exists, the EJB container will call ejbActivate(), followed by the ejbLoad() method; the ejbLoad() method is responsible for actually obtaining the data from the database.

ejbActivate()

ejbActivate() is where we put any code for acquiring resources that the bean needs. In this case, the only thing we need is a database connection.

 public void ejbActivate() {   System.out.println("--- ejbActivate ---");   getConnection(); } 
ejbLoad()

This is where we put the code for reading values from the database into our instance variables. Notice that this method takes no parameters even though we need the primary key to obtain a record from the database. To obtain the primary key, we need to call the entity context method getPrimaryKey(). We can then use an SQL SELECT statement to read the bean's attributes from the database.

 public void ejbLoad() {   System.out.println("--- ejbLoad ---");   try   {     this.email = (String) entityContext.getPrimaryKey();     String sql = "SELECT USERNAME, PASSWORD, MEMBER_SINCE " +                  "FROM USER_INFO WHERE EMAIL=?";     PreparedStatement ps = conn.prepareStatement(sql);     ps.setString(1, email);     ResultSet rs = ps.executeQuery();     if(rs.next())     {       username = rs.getString(1);       password = rs.getString(2);       memberSince = rs.getDate(3);     }     ps.close();   }   catch(SQLException e)   {     System.out.println("ejbLoad() caught: " + e);   } } 

Once this method has been called, our bean is ready to use and we can call the business methods that we create such as the getter/setter methods that we'll see below.

ejbStore()

This method updates the data in the database. When this happens is entirely up to the EJB container; we are guaranteed only that any changes we make to the bean will be preserved. We use an SQL UPDATE statement to update the record using the primary key in the WHERE clause.

 public void ejbStore() throws RemoteException {   System.out.println("--- ejbStore ---");  boolean success = false;  try  {   String sql = "UPDATE USER_INFO SET " +                "USERNAME = ?, " +                "PASSWORD = ?, " +                "WHERE EMAIL = ? ";   PreparedStatement ps = conn.prepareStatement(sql);   ps.setString(1, username);   ps.setString(2, password);   ps.setString(3, email);   success = (ps.executeUpdate() == 1);   ps.close();   }   catch (SQLException e)   {      System.out.println("ejbStore() caught: " + e);   }   if(!success)   {     throw new RemoteException("Failed to update");   } } 

Notice that, because we don't allow the memberSince attribute to be changed once the bean is created, we don't need to update it here. Also note that it makes no sense to update the primary key.

ejbPassivate()

This method is called by the container when it is going to put the bean in an inactive state. This may be because the client application closed or timed out, or because the EJB container is running low on resources and decides to let another client use this bean. This method gives us a chance to release any resources and do any necessary housekeeping.

Confusing as it may sound, a bean instance is not associated with a specific set of data. As a performance optimization, the EJB container maintains a pool of bean instances. It may grow or shrink the pool depending on load, but in general, existing bean instances are reused as clients come and go.

To disassociate the bean instance from a specific set of bean data, it first calls ejbStore(), then calls ejbPassivate(). When the EJB container assigns the bean instance to another client application (or gives it back to ours), it will call ejbActivate() to reacquire the resources we need and ejbLoad() with the appropriate primary key to associate the bean instance with the right set of data.

The only resource our EJB holds that we need to release is the database connection.

 public void ejbPassivate() {   System.out.println("--- ejbPassivate ---");   disconnect(); } 
ejbRemove()

When we want to get rid of a bean permanently, we call the remote interface's remove() method. This causes the EJB container to call the bean implementation's ejbRemove() method where we must provide code to remove the bean's data permanently from the database using the SQL DELETE statement.

 public void ejbRemove() throws RemoteException {   System.out.println("--- ejbRemove ---");   boolean success = false;   try   {     String sql = "DELETE FROM USER_INFO WHERE " +                   "EMAIL = ?";     PreparedStatement ps = conn.prepareStatement(sql);     ps.setString(1, this.email);     success = (ps.executeUpdate() == 1);     ps.close();     disconnect();   }   catch(SQLException e)   {     System.out.println("ejbRemove() caught: " +e);   }   if(!success)   {     throw new RemoteException("Failed to delete record");   } } 

Note that we also close our database connection after deleting the record.

setEntityContext(), unSetEntityContext()

The method setEntityContext() provides an interface, the EntityContext, that our bean can use to access information about the EJB container. We used this context above in the ejbLoad() method to get the bean's primary key. The method unSetEntityContext() dereferences this context. JDeveloper provides these methods for us; we don't need to change them.

 public void setEntityContext(EntityContext ctx) {   System.out.println("--- setEntityContext ---");   this.entityContext = ctx; } public void unsetEntityContext() {   System.out.println("--- unsetEntityContext ---");   this.entityContext = null; } 
getConnection(), disconnect()

These are private helper methods that we create to manage the database connection. They are called from some of the methods above.

 private void getConnection() {   System.out.println("*** getConnection() ***");   if(conn == null)   {     try     {       InitialContext ic=new InitialContext();       javax.sql.DataSource ds =                (javax.sql.DataSource)(ic.lookup("jdbc/OsirisDS"));       conn= ds.getConnection();     }     catch(Exception e)     {       System.out.println("getConnection() caught: " + e);     }   } } private void disconnect() {   System.out.println("*** disconnect() ***");   if(conn!=null)   {     try     {       conn.close();       conn = null;     }     catch(SQLException e)     {       System.out.println("disconnect() caught: " + e);     }   } } 

Notice that the code for connecting is identical to the code we used in the session bean. We use JNDI to locate a datasource specified in the default data-source.xml file.

Business Methods

In addition to writing methods that allow the EJB container to manage our EJB, we also need to write the methods to implement the functionality that our application will require in order to use the bean. These are often called business methods, and in the case of entity beans, they commonly consist of getter and setter methods.

Getter/setter methods are identical to the methods that a JavaBean might have; however, it is important to remember that they are executed remotely. This means that the invocation of each method may require a round trip over the network.

   public String getUsername()   {     return username;   }   public String getEmail()   {     return email;   }   public String getPassword()   {     return password;   }   public java.util.Date getMemberSince()   {     return memberSince;   }   public void setUsername(String username)   {     this.username = username;   }   public void setPassword(String password)   {     this.password = password;   }   public String getHello()   {     return "Hello";   } } 
Possible Improvements

This example is intended to show only a basic BMP bean implementation and it leaves some room for improvement. Because it is likely that all the data will be required by a client application, one improvement would be to add a getter method that returns all the user information in a single value object, as in the previous session bean example, so that only a single round trip is required to obtain the data.

Another possible improvement, also exemplified in the session bean, would be to use a data access object to encapsulate access to the database.

The Remote Interface UserInfo

As noted previously, the client application interacts with the bean implementation, UserInfoBean, indirectly by means of the remote interface.

To be able to call the business methods that we created in the bean implementation, we need to include them in the remote interface, too. Because the methods in the remote interface are called remotely, we need to declare that they may throw a RemoteException.

 package mypackage4; import javax.ejb.EJBObject; import java.rmi.RemoteException; import java.util.Date; public interface UserInfo extends EJBObject {   public String getUsername()throws RemoteException;   public String getEmail()throws RemoteException;   public String getPassword()throws RemoteException;   public Date getMemberSince()throws RemoteException;   public void setUsername(String username)throws RemoteException;   public void setPassword(String password)throws RemoteException;   public String getHello() throws RemoteException; } 
The Home Interface UserInfoHome

The main use of the home interface is to obtain beans. Typically, there are three ways to obtain entity beans:

  • Create a new bean.

  • Find an existing bean.

  • Find a set of beans.

To create a bean, we need a set of ejbCreate() and ejbPostCreate() methods in the bean implementation and a create() method here, in the home interface, with matching parameters. We can have more than one set of these methods, but as we've seen in the bean implementation, for this sample EJB we have one only set, which takes three parameters, email, username, and password.

 package mypackage4; import javax.ejb.EJBHome; import java.rmi.RemoteException; import javax.ejb.CreateException; import javax.ejb.FinderException; public interface UserInfoHome extends EJBHome {   UserInfo create(String email,       String username, String password)     throws RemoteException, CreateException; 

Create methods are optional; if we don't want to allow client applications to create beans, we don't need to provide them. Presumably, beans would be created in some other way perhaps using a separate administrator application or a bulk loader that imports the data from another source.

In addition to create() methods, the home interface can have finder methods to allow us to locate an existing bean or beans. One finder method is required, findByPrimaryKey().

   UserInfo findByPrimaryKey(String primaryKey)       throws RemoteException, FinderException; } 

The implementation in UserInfoBean, as we've seen, is a method called ejbFindByPrimaryKey(). This method is guaranteed to return a single object that implements our UserInfo remote interface.

We haven't done so in this example, but we could also have provided additional finder methods that return multiple EJB objects. We might, for example, have declared a method like this in our home interface:

 Collection findAll()    throws RemoteException, FinderException; 

The corresponding ejbFindAll() method in the bean implementation would be required to find the primary keys of all the records in the USER_INFO table and return them to the EJB container in a vector. The EJB container would then call the ejbLoad() for each primary key to build a collection of UserInfo objects.

Deployment Descriptor, Deploying the UserInfo EJB

We don't need to make any changes to the deployment descriptor as generated by JDeveloper:

[View full width]

<?xml version = '1.0' encoding = 'windows-1252'?> <!DOCTYPE ejb-jar PUBLIC "-//Sun Microsystems, Inc.//DTD Enterprise JavaBeans 1.1//EN" graphics/ccc.gif"http://java.sun.com/j2ee/dtds/ejb-jar_1_1.dtd"> <ejb-jar> <enterprise-beans> <entity> <description>Entity Bean ( Bean-managed Persistence )</description> <display-name>UserInfo</display-name> <ejb-name>UserInfo</ejb-name> <home>mypackage8.UserInfoHome</home> <remote>mypackage8.UserInfo</remote> <ejb-class>mypackage8.impl.UserInfoBean</ejb-class> <persistence-type>Bean</persistence-type> <prim-key-class>java.lang.String</prim-key-class> <reentrant>False</reentrant> </entity> </enterprise-beans> </ejb-jar>

Following the directions given in the session bean example, you may either run the bean using the OC4J server embedded in JDeveloper or deploy it to a standalone instance of OC4J.



Java Oracle Database Development
Java Oracle Database Development
ISBN: 0130462187
EAN: 2147483647
Year: 2002
Pages: 71

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net