[ Team LiB ] Previous Section Next Section

Creating BMP Entity Beans

The DoctorEJB Entity bean represents information about a doctor. Its attributes are an ID, first name, last name, and specialty. The ID will be an Integer and it will be the primary key. Because this class is already defined and we're using a single value for the primary key, we don't need a custom primary key class for this Entity bean. In this section, we're going to create an Entity bean with BMP that uses JDBC to access a single table named Doctor in a data store. The database definition for this table is shown in Listing 22.1.

Listing 22.1 Doctor.ddl
CREATE TABLE Doctor (
  ID INTEGER NOT NULL,
  FIRSTNAME VARCHAR(20),
  LASTNAME VARCHAR(20),
  SPECIALTY VARCHAR(20)
);

Creating the Home Interface

The Home interface for the DoctorEJB Entity bean contains creation methods and finder methods.

Creation Methods

This Entity bean will have one creation method that utilizes a DoctorVO value object. Clients wanting to create a DoctorEJB instance must create an instance of DoctorVO locally, fill it with information, and then pass it to the create method.


public Doctor create(DoctorVO doctorVO) throws CreateException, RemoteException;
Finder Methods

Clients of this DoctorEJB need to locate doctors in four ways: by their ID, by their first and last name, and by their specialty. Because we've decided to use the ID as our primary key, the findByPrimaryKey uses the ID as a parameter.



public Doctor findByPrimaryKey(Integer primaryKey) throws FinderException, RemoteException;
public Collection findByName(String firstName, String lastName) throws FinderException,
graphics/ccc.gif RemoteException;
public Collection findBySpecialty(String specialty) throws FinderException, RemoteException;

Because primary keys are unique, findByPrimaryKey returns zero or one instance of DoctorEJB. However, the other findBy methods could return multiple DoctorEJBs; therefore, their return values are collections of DoctorEJB objects. Listing 22.2 shows the complete code for the Home interface.

Listing 22.2 DoctorHome.java
package wls8unleashed.ejb.entity.bmp;
import javax.ejb.CreateException;
import javax.ejb.EJBHome;
import javax.ejb.FinderException;
import java.rmi.RemoteException;
import java.util.Collection;

public interface DoctorHome extends EJBHome {
  public Doctor create(DoctorVO doctorVO) throws CreateException, RemoteException;
  public Doctor findByPrimaryKey(Integer primaryKey) throws FinderException, RemoteException;
  public Collection findByName(String firstName, String lastName) throws FinderException,
graphics/ccc.gif RemoteException;
  public Collection findBySpecialty(String specialty) throws FinderException, RemoteException;
}

Creating the Value Object

Because our Entity bean's creation method expects a DoctorVO object, we should create it here. The client uses this value object to retrieve and modify existing DoctorEJBs. This value object is a JavaBean that contains private instance variables to hold the doctor's ID, first and last names, and specialty. It also contains public getter and setter methods to access these attributes. Because we'll be using this object as a parameter in an RMI call, it should be serializable. In addition to getters and setters for all attributes, a handy toString method has been included for debugging purposes. Listing 22.3 shows the full code.

Listing 22.3 DoctorVo.java
package wls8unleashed.ejb.entity.bmp;

import java.io.Serializable;

public class DoctorVO  implements Serializable {
        private Integer id;
        private String  firstName;
        private String  lastName;
        private String  specialty;

        public DoctorVO() {
        }

        public Integer getId(){
            return id;
        }

        public void setId(Integer id) {
            this.id = id;
        }

        public String getFirstName(){
            return firstName;
        }

        public void setFirstName(String firstName){
            this.firstName = firstName;
        }

        public String getLastName(){
            return lastName;
        }

        public void setLastName(String lastName){
            this.lastName = lastName;
        }

        public String getSpecialty(){
            return specialty;
        }

        public void setSpecialty(String specialty){
            this.specialty = specialty;
        }

        public String toString() {
            StringBuffer result = new StringBuffer("[DoctorVO ");
            result.append(" id: "       + id);
            result.append(" firstName:" + firstName);
            result.append(" lastName:"  + lastName);
            result.append(" specialty:" + specialty);
            result.append("]");
            return result.toString();
        }
}

Creating the Remote Interface

The Remote interface is used to declare the business methods of the Entity bean. The Entity bean's clients call these methods. Because we're creating a Web application, our clients will be servlets and JavaServer Pages. In addition to creating getter and setter methods for each attribute of DoctorEJB, we'll also create bulk getter and setter methods that accept a DoctorVO object and return one. In this way, we can store and retrieve the full state of a DoctorEJB with one EJB method call. This is an example of a coarse-grained method and is an excellent way to reduce the network overhead involved in making multiple remote method calls. The full listing for the Remote interface is shown in Listing 22.4.

Listing 22.4 Doctor.java
package wls8unleashed.ejb.entity.bmp;

import java.rmi.RemoteException;
import javax.ejb.EJBObject;

public interface Doctor extends EJBObject {
    public Integer getId() throws RemoteException;
    public String getFirstName() throws RemoteException;
    public void setFirstName(String firstName) throws RemoteException;
    public String getLastName() throws RemoteException;
    public void setLastName(String lastName) throws RemoteException;
    public String getSpecialty() throws RemoteException;
    public void setSpecialty(String specialty) throws RemoteException;
    public DoctorVO getDoctorData() throws RemoteException;
    public void setDoctorData(DoctorVO doctorVO) throws RemoteException;
}to declare the business methods of the Entity bean. The Entity

Creating the Bean Class

So far, we've declared methodsto declare the business methods of the Entity bean. The Entity in the Home and Remote interfaces. We now implement those methods in the bean class. Because the class implements the EntityBean interface, let's discuss how the various methods of the interface and the methods of the methods associated to the Home interface will be implemented.

  • ejbCreate()— Our ejbCreate method takes a DoctorVO object as a parameter. This method extracts attributes from the DoctorVO object, updates Entity bean instance variables, and performs an SQL INSERT into to the database table.

  • ejbPostCreate()— The ejbPostCreate method is empty because we don't need more initialization than what is performed in ejbCreate.

  • ejbLoad()— This method reads the primary key from the EntityContext, performs a SELECT into the database using that key, and initializes the Entity bean attributes with the database information.

  • ejbRemove()— This method reads the primary key from the EntityContext and performs a DELETE of the table row with this key.

  • ejbStore()— This method reads the primary key from the EntityContext and performs an UPDATE of the table row with this key.

Some comments regarding the following code are in order. We create a static final variable called DATASOURCE with a value of java:comp/env/TxDataSource. That isn't the actual name of the JDBC data source. It's a reference to an external resource that is qualified in the deployment descriptor. The code that performs a JNDI lookup of the data source and grabs an available connection has been consolidated in the getConnection method. The code to release the JDBC resources is in the releaseResources method. Also note that there is no setId method. After an Entity bean has been created, its primary key should never change. Listing 22.5 shows the full code for the bean class.

Listing 22.5 DoctorEJB.java
package wls8unleashed.ejb.entity.bmp;

import javax.ejb.*;

import java.util.*;
import java.sql.*;
import javax.sql.*;
import javax.naming.*;


public class DoctorEJB implements EntityBean {

    private EntityContext ctx;
    private static final String DATASOURCE = "java:comp/env/TxDataSource";

    private Integer id;
    private String firstName;
    private String lastName;
    private String specialty;

    public void setEntityContext(EntityContext ctx) {
        this.ctx = ctx;
    }

    public void unsetEntityContext() {
        this.ctx = null;
    }

    public void ejbActivate() {}
    public void ejbPassivate() {}

    public Integer ejbCreate(DoctorVO vo) throws CreateException
    {
        System.out.println("DoctorEJB ejbCreate DoctorVO = " + vo.toString());

        Connection conn = null;
        PreparedStatement ps = null;

        try {
            conn = getConnection();

            id = vo.getId();
            firstName = vo.getFirstName();
            lastName = vo.getLastName();
            specialty = vo.getSpecialty();

            ps = conn.prepareStatement("INSERT INTO Doctor (ID, FIRSTNAME,
               LASTNAME, SPECIALTY) VALUES (?,?,?,?)");
            ps.setInt(1, id.intValue());
            ps.setString(2, firstName);
            ps.setString(3, lastName);
            ps.setString(4, specialty);

            int numrows = ps.executeUpdate();
            if (numrows != 1)
                throw new CreateException("Cannot create Doctor numrows = " + numrows);
            ps.close();

            return id;

        } catch (Exception e) {
            throw new CreateException("Cannot create Doctor " + e.getMessage());
        } finally {
            closeResources(ps, conn);
        }
    }

    public void ejbPostCreate(DoctorVO vo)
    {
    }

    public void ejbLoad() {

        Integer id = (Integer)ctx.getPrimaryKey();

        try {
            readFromDb(id.intValue());
        } catch (Exception e) {
            throw new EJBException(e);
        }
    }


    public void ejbStore() {
        Connection conn = null;
        PreparedStatement ps = null;

        try {
            conn = getConnection();
            ps = conn.prepareStatement("UPDATE DOCTOR SET FIRSTNAME = ?,
                 LASTNAME = ?, SPECIALTY = ? WHERE ID = ?");

            ps.setString(1, firstName);
            ps.setString(2, lastName);
            ps.setString(3, specialty);

            Integer id = (Integer)ctx.getPrimaryKey();
            ps.setInt(4, id.intValue());

            int numrows = ps.executeUpdate();
            if (numrows != 1)
                throw new EJBException("Cannot update Doctor, id = " + id.toString());
        } catch (Exception e) {
            throw new EJBException(e);
        } finally {
            closeResources(ps, conn);
        }
    }

    public void ejbRemove() {
        Connection conn = null;
        PreparedStatement ps = null;

        try {
            conn = getConnection();
            ps = conn.prepareStatement("DELETE FROM Doctor WHERE ID = ?");

            Integer id = (Integer)ctx.getPrimaryKey();
            ps.setInt(1, id.intValue());

            int numrows = ps.executeUpdate();
            if (numrows != 1)
                throw new EJBException("Cannot delete Doctor, id = " + id.toString());
        } catch (Exception e) {
            throw new EJBException(e);
        } finally {
            closeResources(ps, conn);
        }
    }


    public Integer ejbFindByPrimaryKey(Integer id) throws FinderException {
        try {
            readFromDb(id.intValue());
            return id;
        } catch(Exception e) {
            throw new FinderException(e.getMessage());
        }

    }

    public Collection ejbFindBySpecialty(String specialty) throws FinderException {
        Connection conn = null;
        PreparedStatement ps = null;

        try {
            conn = getConnection();
            ps = conn.prepareStatement("SELECT id FROM Doctor
                 WHERE UPPER(specialty) LIKE UPPER(?)");
            ps.setString(1, specialty);

            Vector doctors = new Vector();
            ResultSet rset = ps.executeQuery();
            while(rset.next()) {
                Integer id = new Integer(rset.getInt(1));
                doctors.addElement(id);
            }
            rset.close();
            return doctors;

        } catch (Exception e) {
            throw new FinderException(e.getMessage());
        } finally {
            closeResources(ps, conn);
        }
    }

    public Collection ejbFindByName(String firstName, String lastName)
           throws FinderException {
        Connection conn = null;
        PreparedStatement ps = null;

        try {
            conn = getConnection();
            ps = conn.prepareStatement("SELECT id FROM Doctor
               WHERE UPPER(firstName) LIKE UPPER(?) AND UPPER(lastName) LIKE UPPER(?)");
            ps.setString(1, firstName);
            ps.setString(2, lastName);

            Vector doctors = new Vector();
            ResultSet rset = ps.executeQuery();
            while(rset.next()) {
                Integer id = new Integer(rset.getInt(1));
                doctors.addElement(id);
            }
            rset.close();
            return doctors;

        } catch (Exception e) {
            throw new FinderException(e.getMessage());
        } finally {
            closeResources(ps, conn);
        }
    }


    private void readFromDb(int id) throws CreateException {

        Connection conn = null;
        PreparedStatement ps = null;

        try {
            conn = getConnection();
            ps = conn.prepareStatement("SELECT ID, FIRSTNAME, LASTNAME,
                 SPECIALTY FROM Doctor WHERE id = ?");
            ps.setInt(1, id);

            ResultSet rset = ps.executeQuery();
            if(!rset.next()) {
                throw new FinderException("Doctor not found, id = " + id);
            } else {
                readFields(rset);
            }

        } catch (Exception e) {
            throw new CreateException(e.getMessage());

        } finally {
            closeResources(ps, conn);
        }
    }


    private void readFields(ResultSet rset) throws SQLException {
        id = new Integer(rset.getInt(1));
        firstName = rset.getString(2);
        lastName = rset.getString(3);
        specialty = rset.getString(4);
    }

    private void closeResources(PreparedStatement ps, Connection conn)
            throws EJBException {
        try {
            if (ps != null)
                ps.close();

            if (conn != null)
                conn.close();

        } catch (Exception e) {
            throw new EJBException(e);
        }
    }


    public Integer getId() { return id; }

    public String getFirstName() { return firstName; }
    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

    public String getLastName() { return lastName; }
    public void setLastName(String lastName) {
        this.lastName= lastName;
    }

    public String getSpecialty() { return specialty; }
    public void setSpecialty(String specialty) {
        this.specialty = specialty;
    }

    public DoctorVO getDoctorData() {
        DoctorVO vo = new DoctorVO();
        vo.setId(getId());
        vo.setFirstName(getFirstName());
        vo.setLastName(getLastName());
        vo.setSpecialty(getSpecialty());
        return vo;
    }

    public void setDoctorData(DoctorVO vo) {
        setFirstName(vo.getFirstName());
        setLastName(vo.getLastName());
        setSpecialty(vo.getSpecialty());
    }

    private Connection getConnection() {
        try {
            InitialContext ic = new InitialContext();
            DataSource ds = (DataSource)ic.lookup(DATASOURCE);
            return ds.getConnection();
        } catch (Exception e) {
            throw new EJBException(e.getMessage());
        }
    }
}

Creating the Deployment Descriptors

Each Entity bean must be accompanied by either two or three deployment descriptors. In the case of BMP Entity beans, only two deployment descriptors are needed.

The J2EE Deployment Descriptor

All EJBs must have their attributes set in ejb-jar.xml and weblogic-ejb-jar.xml. This includes the Home and Component interfaces and bean classes. Additionally, BMP Entity beans have their own requirements. In this section, we go over only the most common ones and leave the advanced settings for a later section in this chapter.

  • <persistence-type>— Can be either Bean or Container. When using BMP, this value is Bean.

  • <prim-key-class>— The fully qualified name of the primary key class. For this EJB, it is java.lang.Integer.

  • <reentrant>— Specifies whether a thread executing a method on this EJB can enter the bean code more than once. This can occur when bean A calls a method on bean B that then calls a method on bean A. If a bean A is specified as non-reentrant, the EJB container disallows this behavior. If a bean A is specified as reentrant, this behavior is permitted, but a bean client might be able to spawn multiple threads and access the same bean simultaneously from all these threads. This could violate the "single-threaded-ness" of the Entity bean and lead to a potentially dangerous situation. With that in mind, try to avoid using reentrant Entity beans. If you must have this behavior, be very cautious in its use.

  • <resource-ref>— As mentioned earlier, the bean class uses a reference to a JDBC data source, not the actual JNDI name. This allows the actual JNDI name to be configurable through deployment descriptors. In ejb-jar.xml, we specify the reference; in weblogic-ejb-jar.xml, we link the reference to the actual JNDI name.

Listing 22.6 shows the full code of ejb-jar.xml.

Listing 22.6 ejb-jar.xml
<?xml version="1.0"?>
<!DOCTYPE ejb-jar PUBLIC '-//Sun Microsystems, Inc.//
DTD Enterprise JavaBeans 2.0//EN' 'http://java.sun.com/dtd/ejb-jar_2_0.dtd'>

<ejb-jar>
  <enterprise-beans>
    <entity>
      <ejb-name>DoctorEJB</ejb-name>
      <home>wls8unleashed.ejb.entity.bmp.DoctorHome</home>
      <remote>wls8unleashed.ejb.entity.bmp.Doctor</remote>
      <ejb-class>wls8unleashed.ejb.entity.bmp.DoctorEJB</ejb-class>
      <persistence-type>Bean</persistence-type>
      <prim-key-class>java.lang.Integer</prim-key-class>
      <reentrant>False</reentrant>
    <resource-ref>
        <res-ref-name>TxDataSource</res-ref-name>
      <res-type>javax.sql.DataSource</res-type>
        <res-auth>Container</res-auth>
    </resource-ref>
    </entity>
  </enterprise-beans>
  <assembly-descriptor>
    <container-transaction>
      <method>
        <ejb-name>DoctorEJB</ejb-name>
        <method-intf>Remote</method-intf>
        <method-name>*</method-name>
      </method>
      <trans-attribute>Required</trans-attribute>
    </container-transaction>
  </assembly-descriptor>
</ejb-jar>
The WebLogic Server Deployment Descriptor

weblogic-ejb-jar.xml is used for WebLogic Server–specific configuration settings. Later in this chapter, we'll explore advanced settings, but for now we need to be concerned only about linking our data source reference to an actual JNDI name.

The <reference-descriptor> tag is used to link our EJB to an external resource. Here we associate the JNDI name doctorDataSource with the reference name TxDataSource. Listing 22.7 shows the code for weblogic-ejb-jar.xml.

Listing 22.7 WebLogic Deployment Descriptor
<?xml version="1.0"?>
<!DOCTYPE weblogic-ejb-jar PUBLIC " -//BEA Systems, Inc.
          //DTD WebLogic 8.1.0 EJB//" "http://www.bea.com/servers/wls810/dtd/
graphics/ccc.gif weblogic-ejb-jar.dtd" >

<weblogic-ejb-jar>
  <weblogic-enterprise-bean>
      <ejb-name>DoctorEJB</ejb-name>
        <reference-descriptor>
        <resource-description>
             <res-ref-name>TxDataSource</res-ref-name>
             <jndi-name>doctorDataSource</jndi-name>
        </resource-description>
    </reference-descriptor>
      <jndi-name>DoctorHome</jndi-name>
  </weblogic-enterprise-bean>
</weblogic-ejb-jar>

BMP Comments

The bean class DoctorEJB.java contains several lines of code. Imagine having 10 or more BMPs and having to write all this code by hand each time. There are lots of chances to make typographical errors.

If you look at the ejbCreate method, you see that the ID for the EJB is extracted from the DoctorVO parameter. Who creates this parameter? The client, of course! Then who is responsible for creating the ID for each doctor? The client? If the ID is the primary key and must remain unique, we do not want the client to be in charge of key generation. We need a scalable way of generating keys that are guaranteed to be unique. Because we're using BMP, we must write this code ourselves. It turns out that there are multiple ways of doing this with different levels of complexity and performance.

NOTE

If you're interested in exploring this topic, we recommend that you read Chapter 5, "Primary Key Generation Strategies," of EJB Design Patterns: Advanced Patterns, Processes, and Idioms by Floyd Marinescu (ISBN: 0471208310). You can also download a PDF version of this book from TheServerSide at http://www.theserverside.com/books/EJBDesignPatterns/downloadbook.jsp.


    [ Team LiB ] Previous Section Next Section