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 article describes some of the challenges with writing large EJB projects, and how some of them can be overcome by the EJBMaker tool, downloadable at the IBM alphaWorks Web site (see Resources).
EJBMaker reads a single CMP EJB description file -- written in XML -- and automatically generates Java source code and database scripts. The tool takes care of the tedious tasks of defining finder and initialization methods for the beans, and it is capable of generating relationship EJBs that associate CMP EJBs with each other. The 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. The EJBMaker is optimized for use with the IBM WebSphere application server, but with some manual tweaking can also be used with other application servers.
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 (see Resources). The most common solution by far is to store the data in a relational database using the Java Database Connectivity API (JDBC) (see Resources for information about 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. 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 particular to the IBM WebSphere application server. Other application server environments (for instance Enhydra or WebLogic)put queries in XML deployment descriptors using proprietary query language formats. Forsuch 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.
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 eachcreate(...)method declared in the EJB home interface. In particular, it must always contain theejbCreate(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. But what if you don_t have the liberty of switching IDEs? In that case you_re pretty much stuck, unless...
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 (see the previous "Anatomy of a CMP Enterprise JavaBean"):
- 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.
This 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 < 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.
<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 < 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.
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. See this example of how to use
these two methods.
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> |
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.
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. By using the view abstraction between the application server and the physical database tables, we are able to keep information stored in a persistence table even if the EJB is redeployed.
This article has given an overview of the challenges with writing entity EJBs, and some of the features of the EJBMaker tool. We encourage you to download the software from alphaWorks and try it out for yourself. Remember to send feedback and suggestions, which will help us to improve future versions of the product.
- 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.
Stefan Edlund is a research associate/staff software engineer at the IBMAlmaden Research Center. He is currently in the Web Technologies department, conducting research and designing Web-based, active, personal information management systems. Stefan has an M.S. degree in computer science from the Royal Institute of Technology in Stockholm. You can contact Stefan at edlund@almaden.ibm.com.




