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.
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.
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.
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.
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 < 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 < 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.
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.
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.
·
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.