New York University

Computer Science Department

Courant Institute of Mathematical Sciences

 

Efficient CMP Development

 

Course Title: Application Servers                                                       Course Number: g22.3033-011

Instructor: Jean-Claude Franchitti                                                        Session: 6

 

 


EJB Component/Programming Model

 


Container Managed Persistence (CMP)

 

Entity Enterprise JavaBeans are in essence wrappers for persistent data -- commonly in the form of relational database tables -- with additional support for transaction control and security. A CMP entity bean is constructed from at least six separate files, having dependencies that cannot be easily verified at compile time. Proper care must be taken to ensure that object-relational database mappings are correct, and ideally the code should be designed to minimize the changes necessary when requirements change. This handout describes some of the challenges with writing large EJB projects. Note that tools are available on the market today to overcome some of these challenges (e.g., EJBMaker tool, downloadable at the IBM alphaWorks Web site).

 

Tools such as IBM’s EJBMaker reads a single CMP EJB description file -- written in XML -- and automatically generates Java source code and database scripts. The tools take care of the tedious tasks of defining finder and initialization methods for the beans, and they are capable of generating relationship EJBs that associate CMP EJBs with each other. EJBMaker keeps track of updates in the XML bean descriptor file, and only regenerates Java code for EJBs that have changed. In addition, adaptor classes are generated enabling quick initialization or data extraction from an EJB using XML. EJBMaker is optimized for use with the IBM WebSphere application server, but with some manual tweaking can also be used with other application servers.

 

Entity Enterprise JavaBeans: CMP and BMP


One of the strengths of EJB technology is that, at least in theory, an EJB is only written once and can be deployed on any EJB-compliant application server. Version 2.0 of the specification, containing technologies such as the EJB query language, brings us even closer to this goal.

 

Even though we are not there yet with the promise of "Write Once, Run Anywhere" capability, EJBs are surprisingly easy to port from one application server environment to another -- especially for Container Managed Persistence (CMP) Entity EJBs. When writing CMP Entity EJBs, the developer does not have to worry about writing code for persistently storing the state of a bean in a database. All such code is automatically generated during the deployment stage on an application server. This decreases some of the flexibility of the bean, but on the other hand it enables more rapid development. Application server designers are free to use any method desired to persistently store and retrieve EJB data, as long as they fulfill the "entity bean component contract for container managed persistence," as defined by the EJB specification. The most common solution by far is to store the data in a relational database using the Java Database Connectivity API (JDBC). This is the technique used by the IBM WebSphere application server and BEA WebLogic (among others).

 

The other entity EJB type is Bean Managed Persistence (BMP) EJB. In this case, it is up to the developer to manually write code for persistently retrieving or storing bean information. BMP is ideal when data needs to be stored in legacy storage systems or file systems. However, the increased flexibility comes at the price of decreased portability. When the application server is not controlling all access to system resources, no guarantees can be made about the existence or compatibility of such resources across different server environments.

 

Anatomy of a CMP Enterprise JavaBean


Unless you have specific requirements that force a BMP implementation, CMP is the better choice. How do you write a CMP bean? The following files need to be created:

 

·         EJB remote interface. This file contains method signatures for any method that accesses or modifies data stored in the bean. The remote interface must extend the javax.ejb.EJBObject interface.

·         EJB remote implementation. This file provides an implementation of all the methods defined in the remote interface, in addition to methods required by the application server (for example, callback methods). To ensure that all the required methods exist, the EJB remote class must implement javax.ejb.EntityBean.

·         EJB home interface. The home interface declares method signatures for any method that creates new instances of the bean, and for all methods that are used to retrieve instances of the bean (finder methods). The EJB home interface extends the javax.ejb.EJBHome interface.

·         EJB key. The key class contains the unique primary key implementation for the bean. Version 2.0 of the EJB specification defines a primary key class as any class that is a legal value type in RMI-IIOP.

·         EJB finder helper interface (optional). This file contains one static java.lang.String field for each finder method declared in the EJB home interface. The strings are initialized with SQL queries executed dynamically when bean instances are retrieved in a finder method.

Note: This file is used by the IBM WebSphere application server. Other application server environments (for instance Enhydra, OpenEjb, or WebLogic) put queries in XML deployment descriptors using proprietary query language formats. For such environments, this file can be ignored and replaced by manual entry of queries into the deployment descriptors.

·         Deployment descriptor. The deployment descriptor is a serialized Java class that contains meta-data about the bean, such as the names for EJB classes and interfaces, and a list of the persistent fields associated with the bean. Since version 1.1 of the EJB specification, the deployment descriptor can be written in XML (subsequently translated into a serialized Java object during the deployment stage).

·         Database scripts. If the application server doesn’t create the required relational database tables used to persistently store the bean data, database scripts can be created for this purpose.

 

Common CMP pitfalls


In addition to making sure that all the files described above exist, there are several pitfalls developers must avoid when writing CMP entity beans. First of all, dependencies between files exist that cannot be verified at compile time:

 

·         Care must be taken to ensure that methods declared in the remote interface are actually implemented in the EJB remote implementation class. If the EJB remote implementation class were to implement the EJB remote interface directly, verification of dependencies could be checked at compile time. However, there is an inherent distinction between the remote interface as being a client side object, while instances of the remote implementation class belong on the server side. If the remote implementation class were to implement the remote interface, it would also have to implement methods declared by the interface extended by the remote interface -- the javax.ejb.EJBObject. This interface contains many methods that do not belong on the server side.

·         Finder methods declared in the EJBHome interface of a bean must be associated with a query that is typically defined in another file. For WebSphere, the EJB finder helper class must contain one static String field for each finder method declared in the EJB home interface, and the name of the field must match the name of the finder method. Enhydra requires that the XML deployment descriptor contains a finder-method-jdbc-mapping element matching the finder methods declared in the EJBHome etc.

·         The EJB remote implementation class must contain one ejbCreate(...) method for each create(...) method declared in the EJB home interface. In particular, it must always contain the ejbCreate(EJBKey...) method, which creates a new instance of the bean using an EJB key instance.

 

Other requirements include:

 

·         Ensuring that XML deployment descriptors contain proper class names for the EJB components and all declarations required for the persistent fields.

·         Getting the object-relational database mappings set up correctly. This includes making sure that persistent fields are mapped to database columns of the proper type, and that the tables are designed to match any special requirements existing in the persistent code automatically generated by the application server. Column order could be one such requirement.

 

You might think after reading all these requirements that writing CMP entity beans is probably not for the faint-hearted. Luckily, there are tools that will assist you in many of these steps. For instance, the IBM VisualAge for Java IDE contains an excellent EJB development environment that will automatically generate lots of the necessary code for you, and even help you with the object-relational database mappings. WebGain Studio and WebGain StructureBuilder from BEA Systems help developers with some of the hairier EJB development tasks by automatically converting UML models into EJB components etc. If you don’t have the liberty of switching IDEs, you can use tool such as IBM’s EJBMaker to assist you.

 

IBM’s EJBMaker Tool


To help resolve some of the trickier issues associated with writing CMP beans, the EJBMaker tool was created. While the tool does generate WebSphere-specific files, modifying the output for other environments is a fairly straightforward task. All information related to the CMP beans -- including bean names, persistent field names and class, and relational database mappings -- are stored in a single file. The format of the file is XML, which is flexible and easy to extend for future versions of the EJBMaker.

 

From this EJBMaker XML file, the following are generated:

 

·         Java source files for each CMP bean. This includes the EJB remote interface, the EJB remote implementation class, the EJB home interface, the EJB key class, and the EJB finder helper interface.

·         XML deployment descriptors. Contains EJB class names, persistent fields and bean attributes for transactions, security and isolation levels.

·         Database scripts. Scripts used to generated persistence tables in a relational database. The scripts are tailored for DB2, but other databases works as well with minor tweaking involved.

 

All the dependencies are taken care of automatically, including the object-to-relational- database mappings. Default finder methods are generated to retrieve instances matching values of each persistent field in the bean, and to retrieve all existing bean instances (getAllInstances() method). This ensures that the bean is flexible enough to be used in many different scenarios. If a special finder method is required, it can be entered directly in the XML description file.

 

The following is an example of a single CMP bean described in the EJBMaker XML format:

 

<?xml version="1.0"?>

 <!DOCTYPE ejbmaker SYSTEM "ejbmaker.dtd">

 <ejbmaker>

       <!—default attributes for beans -->

       <transaction-attr>TX_REQUIRED</transaction-attr>

       <isolation-level>SERIALIZABLE</isolation-level>

       <run-as-mode>SPECIFIED_IDENTITY</run-as-mode>

       <re-entrant>false</re-entrant>

 

       <!—A Simple Account CMP entity EJB -->

       <bean name = "Account" type = "entity">

             <persistent_field dt="int" col_dt="INTEGER">

        ACCTNUMBER

   </persistent_field>

             <persistent_field dt="java.lang.String" col_dt="VARCHAR(13)">

        SSN

   </persistent_field>

             <persistent_field dt="double" col_dt="DOUBLE">

        BALANCE

   </persistent_field>

             <finder_method name="findNegativeAcct">

                   <sql>

        SELECT * FROM EJB.ACCOUNTBEANTBL WHERE BALANCE &lt; 0

    </sql>

             </finder_method>

       </bean>

 </ejbmaker>

 

There are several parts to this file. The transaction-attr, isolation-level, run-as-mode, and re-entrant are taken directly from Sun’s XML EJB deployment descriptor format. These bean attributes are copied as default attributes for all beans into the deployment descriptors generated.

 

The first element encountered after that is:

 

<bean name = "Account" type = "entity">

 

This is the beginning of a new bean declaration. The Java Naming and Directory Interface (JNDI) name of the bean is ‘Account’, and the type of the bean is ‘entity’. The EJBMaker currently only supports the ‘entity’ bean type.

 

The next few lines indicate how to identify a persistent field:

 

<persistent_field dt="int" col_dt="INTEGER">

       ACCTNUMBER

</persistent_field>

 

The first persistent field defined in the Account bean is ACCTNUMBER. The Java class used to store the persistent field is the primitive int class (declared by the dt attribute), and the data type for the database column is declared using the col_dt attribute as INTEGER. Two more persistent fields are defined, SSN and BALANCE.

 

The next few lines indicate how to specify a special finder method:

 

<finder_method name="findNegativeAcct">

       <sql>

       SELECT * FROM EJB.ACCOUNTBEANTBL WHERE BALANCE &lt; 0

     </sql>

 </finder_method>

 

The name of the finder method is findNegativeAcct. Notice that SQL code placed in elements must follow the rules of XML content. In particular this means that reserved XML characters such as the less-than sign must be escaped.

 

Passing the XML file through the EJBMaker generates source code, XML deployment descriptor, and database scripts.

 

Source Code:

 

From the example above, the following Java classes and methods are generated:

 

·         Account.java
Methods: getACCTNUMBER(), setACCTNUMBER(int), getSSN(), setSSN(String), getBalance(), setBalance(double), getKey(), initialize(TXDocument), getXML()

·         AccountBean.java
Methods: getACCTNUMBER(), setACCTNUMBER(int), getSSN(), setSSN(String), getBalance(), setBalance(double), getKey(), initialize(TXDocument), getXML(), ejbActivate(), ejbLoad(), ejbPassivate(), ejbStore(), ejbRemove(), setEntityContext(), unsetEntityContext()

·         AccountHome.java
Methods: create(AccountKey), findByPrimaryKey(AccountKey), findAllInstances(), findByACCTNUMBER(int), findBySSN(String), findByBALANCE(double), findNegativeAcct()

·         AccountKey.java
Methods: AccountKey(), AccountKey(String), equals(Object)

·         AccountBeanFinderHelper
No methods

·         AccountXMLAdaptor
Methods: AccountXMLAdaptor(), AccountXMLAdaptor(TXDocument), getXML(), setACCTNUMBER(Integer), getACCTNUMBER(), setSSN(String), getSSN(), setBALANCE(Double), getBALANCE()

 

Some of the methods merit further explanation. The remote interface (Account.java), and the implementation of the remote interface (AccountBean.java) have a method called getKey(). The method simply returns the primary key of the bean as a String instance, instead of as an AccountKey instance which is returned by the superclass’ getPrimaryKey() method.

 

The remote interface and the remote implementation class also have the initialize(TXDocument) and the getXML() method. These two methods are used to quickly initialize a bean using XML and to extract information stored in a bean as XML. The use of these two methods is illustrated below:

 

public class AccountTest {

  public static void main(String args[])

  {

         try {

             Properties p = new Properties();

              p.put(Context.INITIAL_CONTEXT_FACTORY,

"com.ibm.ejs.ns.jndi.CNInitialContextFactory");

             p.put(Context.PROVIDER_URL, "IIOP:///");

             InitialContext ic = new InitialContext(p);           

             Object aobj = ic.lookup("Account");

              AccountHome aHome = (AccountHome) javax.rmi.PortableRemoteObject.narrow(aobj, AccountHome.class);

             Account acct = aHome.create(new AccountKey("pk1234"));

 

             // Initialize the new account

             AccountXMLAdaptor xmladaptor = new AccountXMLAdaptor();

             xmladaptor.setACCTNUMBER(new Integer(1111));

             xmladaptor.setSSN("123-45-6789");

             xmladaptor.setBalance(new Double(1000000.00));

             acct.initialize(xmladaptor.getXML());

 

             // Retrieve bean information in XML

             Account acct = aHome.findByPrimaryKey(new AccountKey("pk1234"));

             AccountXMLAdaptor xmladaptor = new AccountXMLAdaptor(acct.getXML());

             int acctnr = xmladaptor.getACCTNUMBER().intValue();

             String SSN = xmladaptor.getSSN();

             double balance = xmladaptor.getBALANCE().doubleValue();

          } catch(Exception e) {

             e.printStackTrace();

          }

  } // main

} // AccountTest

 

The code also explains how to use the AccountXMLAdaptor class. It is a wrapper around an Account XML document, and contains methods to set and get properties associated with Accounts. Observe that persistent fields of a primitive Java classes are converted to their equivalent non-primitive class in the adaptor. This way it is possible to use null values to represent the non-initialized state, which is useful for setting only a subset of the persistent fields. All information related to an Account is encoded in XML using the getXML() method of adaptor class. The XML format is straightforward; the root element is always the name of the EJB, which encloses one element for each persistent field. For the Account example, an EJB instance could be serialized into the following XML document:

 

<Account>

  <ACCTNR>1111</ACCTNR>

       <SSN>123-45-6778</SSN>

       <BALANCE>1000000</BALANCE>

 </Account>

 

Relationships among beans:

 

The EJBMaker can also generate EJBs that are used to represent many-to-many relationships between beans. For example, consider a simple calendar application. Two CMP entity beans are used; one contains information about calendar events (called Event), and the other contains information about the clients of the calendar application (called Client). We now want to represent clients that attend particular calendar events. A crude solution is to add a field to the Event bean containing the primary keys of all clients attending that particular event. The primary keys could be concatenated, using, for instance, comma separation. To retrieve all clients attending an event, code would have to be written that takes the comma-separated string of primary keys and does a lookup using findByPrimaryKey(...) in the Client home interface for each of the keys. Even if the code is written inside a session bean, performance will be slow compared to the second solution.

 

A more elegant solution would be to use an additional table to represent the relationship between events and clients. Two columns contain primary keys for Event EJB instances and Client EJB instances. Now the task of retrieving all clients participating in an event is simply a matter of writing a finder method in the Client home interface, and an SQL query in the helper finder class. This is exactly what the EJBMaker does. A relationship between two beans is constructed using the following technique:

 

<ejbmaker>

  <!—Event and Client EJB declarations go here -->

  <relation name = “Attendee”>

             <bean_ref_1>Event</bean_ref_1>

             <bean_ref_2>Client</bean_ref_2>

  </relation>

 </ejbmaker>

 

The relationship is represented by a new CMP entity bean called Attendee, having two fields (three if we count the primary key field). The fields contain primary key instances of Event EJBs and Client EJBs. In addition, two additional finder methods are created, one in the Event home interface and one in the Client home interface. The Event home interface finder method is:

 

java.util.Enumeration findByAttendee(String pkey)

 

‘pkey’ is a primary key of a Client EJB. The method returns an Enumeration of the events that a particular client is attending. The client home interface has the same method:

 

java.util.Enumeration findByAttendee(String pkey)

 

However, in this class ‘pkey’ is a primary key of the Event EJB, and the method returns an Enumeration of clients attending a particular event.

 

Database Scripts


The final pieces of code generated by the EJBMaker are database scripts for creating persistent EJB tables. One script is created for each EJB, in addition to the CreateTables.ddl script that creates all the required tables. However, the tables are not ready to be used by the application server until virtual tables (or Views) have been created. The views shuffle around the column order of the tables so that the columns correspond to the order expected by the generated EJB persistence code. The EJBMaker comes with a tool that generates the CreateViews.ddl script for you. The script must be regenerated every time an EJB is redeployed, or the columns might end up in the wrong order. The generation tool uses the view abstraction between the application server and the physical database tables to keep information stored in a persistence table even if the EJB is redeployed.

 

Conclusion


This handout has given an overview of the challenges with writing entity EJBs, and some of the features of the EJBMaker tool. You are encouraged to download that particular software from alphaWorks and try it out for yourself.

 

Resources

 

·         Download EJBMaker from alphaWorks.

·         Get EJB downloads and specifications.

·         Learn more about Java Database Connectivity API.

·         Visit the IBM WebSphere Application Server Web site for more information, including downloads.

·         Visit the BEA WebLogic Application Server Web site for more information, including downloads.