As the EJB 2.0 specification has entered its final stage,
many companies are in the process of building server-side J2EE
applications. The final draft of the specification has made
container-managed persistence (CMP) of entity beans complete and more
powerful.
Significant changes that were made to the specification in
the area of entity beans include:
- Local entity beans are lightweight entity beans that exist
only locally, inside the EJB container, and therefore don't need to
be marshaled.
- EJB QL helps create specialized queries declaratively.
- Declarative entity bean relationships have been added.
- Dependent object classes were removed from the latest draft
in favor of local entity beans.
These features are essential for real-life applications.
Another exciting development is that the latest version of BEA
WebLogic application server supports the draft specification in all
of the preceding features. In this article I will go through an
example application to demonstrate local entity beans and entity bean
relationships; namely, a many-to-many relationship. I'll also create
deployment descriptors for BEA WebLogic Application Server 6.1 and
write a build-script to generate a deployable archive.
Remote entity beans encapsulate coarse-grained persistent
objects, whereas local entity beans target fine-grained persistent
objects. Local entity beans differ from remote entity beans in this
manner:
- The bean interface extends javax.
ejb.EJBLocalObject; the home interface extends
javax.ejb.EJBLocalHome.
- The bean is never transmitted over the network. Thus its
methods will not throw a java.rmi.RemoteException.
- The bean is collocated in the same JVM and its method
parameters are passed by reference.
Although the last item exposes the implementation details
about the object, this optimization is well deserved. Indeed, it's
only logical that some objects are never accessed remotely and
therefore shouldn't incur the overhead associated with the remote
call. Often the local entity bean may be accessed through a facade
session bean. In this case the remote clients will pass value objects
to the facade session bean. This bean will then perform business
logic that may involve other session beans and entity beans. The
facade bean will call the local entity beans when the data needs to
be persisted. (More about this later.)
Another important feature of CMP is declarative entity bean
relationships. The specification allows a declaration of the
following things about a relationship:
- Cardinality: One-to-one, one-to-many, and many-to-many
relationships are supported.
- Direction: Unidirectional and bidirectional relationships are
supported.
- Cascade deletes: If a relationship is marked for cascade
delete, the dependent entity beans are removed when the main entity
bean is removed.
You still have to provide abstract get and set methods in the
bean class to reflect cardinality and direction. For example, if an
Order bean has many Order Line Items, then you must provide abstract
getLineItems and setLineItems methods in the Order class to specify a
unidirectional, one-to-many relationship.
How It's Done
Let's write a small application that utilizes local entity
beans and relationships. My example will run on BEA WebLogic 6.1
Server and an Oracle database. I'll have two entity beans - User and
Group. User can belong to multiple groups and a Group can have
multiple users. Both beans are local entity beans and have two
session facade beans: UserManager and GroupManager. I also want to
navigate in both directions. Thus our entity beans have a
many-to-many, bidirectional relationship. Additionally, I'll have two
value objects that are sent to clients by the facade beans:
UserValueObject and Group-
ValueObjects. The value objects allow clients to cache and use
objects multiple times without remote calls. Figure 1 illustrates the
classes.
In Figure 1 the entity beans' interfaces extend
javax.ejb.EJBLocalObject and the entity beans' home interfaces extend
the javax.ejb.EJBLocalHome interface. Another important aspect is
that the value object must implement the java.io.Serializable
interface for it to be sent over the network.
Figure 1:
The entity beans have abstract get and set methods for all
fields that need to be persisted. The business logic to create,
update, and delete the entity beans resides in the facade session
beans. Here's how the User
Manager bean creates a User in the createUser method:
InitialContext ctx = new InitialContext();
UserBeanHome home = (UserBeanHome)ctx.lookup("User");
UserBean bean = home.create(user.getUserName());
The Group Manager bean is responsible for adding and removing
Users from Groups. Adding a User to a Group is as simple as adding a
user bean to a collection. This is done in the addUserToGroup method.
I had to declare this method to require a transaction in the
ejb-jar.file because the collection must be retrieved and altered in
the same transaction.
UserBean userBean = userHome.findByPrimaryKey(userName);
GroupBean groupBean = home.findByPrimaryKey(groupName);
Collection users = groupBean.getUsers();
users.add(userBean);
Once I have the classes, I can write the deployment
descriptors. First I write ejb-jar.xml the same as for any EJB
container. Listing 1 describes a Group-User relationship.
Ejb-jar.xml descriptor declares the entity beans and
relationships, whereas the actual mapping-to-storage mechanism is
done in the EJB container-specific deployment descriptors. BEA
WebLogic 6.1 uses two files for that purpose. The first file,
weblogic-ejb-jar.xml, specifies a persistence descriptor as a part of
the Weblogic-enterprise-bean element. It also specifies the name of
the second descriptor file where the field mapping is done. Listing 2
shows the complete Weblogic enterprise bean element.
I specify how the fields are mapped to the table columns in
the other WebLogic descriptor, weblogic-cmp-rdbms-jar.xml. I also
specify how the Group-User relationship is mapped to the database
through a USER_GROUP table. Listing 3 shows the relationship portion.
Once I've written the deployment descriptors and implemented
the beans, I write a build script to build my application. Since EJB
applications have a lot of different pieces to them, it's a good idea
to create a build script as soon as the skeleton of the project is in
place. I use a utility Ant to do all my builds. Ant can create
directories, move files, compile, jar, and deploy your J2EE
application. It can even create database schema and run unit tests
for you.
Another good thing about Ant is that the scripts are written
in XML, which seems quite natural since we spent all this time with
XML writing the deployment descriptors. WebLogic Server 6.1 comes
with a number of examples that have Ant build scripts and can serve
as templates. Listing 4 shows the fragment of the Ant script that
calls the WebLogic ejb compiler to create a final deployable .jar
file for our beans.
Finally, I copy the output .jar file to the WebLogic
applications directory and run the Java client. The client creates
the UserManager session bean and calls UserManager to create a User,
passing a User value object (see Listing 5). Groups are created in a
similar way. After a Group is created, I can add a User to it with
just one call.
groupManager.addUserToGroup("alex", "testgroup");
Under the hood, the container inserts a row in the table
USER_GROUP that points to tables SYSUSER and SYSGROUP connecting them.
When testing my applications on WebLogic 6.1 I used a utility
P6Spy available from Provision6
(www.provision6.com/)
to analyze the SQL generated by the container. P6Spy
driver is configured to log all SQL statements before they get to the
real JDBC driver.
One thing I'd see from the SQL log is that for every get
method called on an entity bean, the container generates a SELECT
statement. To improve that you can use different concurrent strategy
flags in the WebLogic descriptor. For example, if you set concurrency
strategy to be Exclusive, then the SELECT statement is issued only
once, the first time the bean is loaded. The assumption here is that
all updates are going through the bean, so the bean doesn't have to
be synchronized with the database for every get method. (See WebLogic
documentation for more on other concurrent strategy flags and entity
bean optimization.)
Similarly, for every set method on the entity bean, the
container issues an UPDATE statement. The reason is that every set
method is performed in a new transaction, and at the end of each
transaction the container commits the data to storage. Again, we can
improve that if all set methods are called in the same transaction.
Since they're called inside the facade session bean, we can mark the
bean to require a transaction in the ejb-jar.xml deployment
descriptor. Further optimizations are possible as required by a
specific application.
Entity beans separate business logic from persistence code.
Local entity beans cover the area of fine-grained objects making
necessary syntactical changes to optimize performance. Declarative
relationships take another step to abstract object developers from
the database schema.
These features of CMP have the potential to increase the
productivity of the enterprise application developers. The situation
will further improve once EJB container vendors create or improve
graphical tools to write deployment descriptors for CMP and
relationships. For now the example deployment descriptors given in
this article can help you get started.
For More Information
1. EJB 2 Proposed Final Draft 2:
http://java.sun.com/products/ejb/docs.html
2. BEA Weblogic Server Release 6.1 Documentation:
http://e-docs.bea.com/wls/docs61/index.html
3. Jakarta Ant Project:
http://jakarta.apache.org/ant/
4. P6Spy Driver:
www.provision6.com/index.htm
Author Bio
Alex Pestrikov, a programmer for more than five years, has spent the last three years
working on Java projects. He currently writes J2EE applications for the Middleware Lab, government
of Ontario, Canada. Alex holds a degree in finance and computer science from the University of Bridgeport (Connecticut), and is a master's candidate in computer science.
pestrikov@yahoo.com
Listing 1: ejb-jar.xml
<relationships>
<ejb-relation>
<ejb-relation-name>Group-User</ejb-relation-name>
<ejb-relationship-role>
<ejb-relationship-role-name>
group-has-users
</ejb-relationship-role-name>
<multiplicity>many</multiplicity>
<relationship-role-source>
<ejb-name>Group</ejb-name>
</relationship-role-source>
<cmr-field>
<!-- method to retrieve users
is java.util.Collection getUsers() -->
<cmr-field-name>users</cmr-field-name>
<!-- you can specify java.util.Set or java.util.
Collection if multiplicity is many.
You do not have to provide this element if multiplicity is one -->
<cmr-field-type>java.util.Collection</cmr-field-type>
</cmr-field>
</ejb-relationship-role>
<ejb-relationship-role>
<ejb-relationship-role-name>
user-belongs-to-group
</ejb-relationship-role-name>
<multiplicity>many</multiplicity>
<relationship-role-source>
<ejb-name>User</ejb-name>
</relationship-role-source>
<cmr-field>
<cmr-field-name>groups</cmr-field-name>
<cmr-field-type>
java.util.Collection
</cmr-field-type>
</cmr-field>
</ejb-relationship-role>
</ejb-relation>
</relationships>
2: weblogic-ejb-jar.xml
<weblogic-enterprise-bean>
<ejb-name>User</ejb-name>
<entity-descriptor>
<persistence>
<persistence-type>
<type-identifier>
WebLogic_CMP_RDBMS
</type-identifier>
<type-version>6.0</type-version>
<type-storage>
META-INF/weblogic-cmp-rdbms-jar.xml
</type-storage>
</persistence-type>
<persistence-use>
<type-identifier>
WebLogic_CMP_RDBMS
</type-identifier>
<type-version>6.0</type-version>
</persistence-use>
</persistence>
</entity-descriptor>
<local-jndi-name>User</local-jndi-name>
</weblogic-enterprise-bean>
Listing 3: weblogic-cmp-rdbms-jar.xml
<weblogic-rdbms-relation>
<relation-name>Group-User</relation-name>
<table-name>USER_GROUP</table-name>
<weblogic-relationship-role>
<relationship-role-name>
group-has-users
</relationship-role-name>
<column-map>
<!-- column from USER_GROUP table>
<foreign-key-column>
GROUPNAME
</foreign-key-column>
<!-- column from SYSUSER table>
<key-column>GROUPNAME</key-column>
</column-map>
</weblogic-relationship-role>
<weblogic-relationship-role>
<relationship-role-name>
user-belongs-to-group
</relationship-role-name>
<column-map>
<!-- column from USER_GROUP table -->
<foreign-key-column>
USERNAME
</foreign-key-column>
<!-- column from SYSGROUP table -->
<key-column>USERNAME</key-column>
</column-map>
</weblogic-relationship-role>
</weblogic-rdbms-relation>
Listing 4: Ant build script
<!-- Run ejbc to create the deployable jar file -->
<target name="ejbc" depends="jar_ejb">
<java classname="weblogic.ejbc" fork="yes">
<sysproperty key="weblogic.home" value="${WL_HOME}"/>
<arg line="-compiler javac ${dist}/user_in.jar ./user_out.jar"/>
<classpath>
<pathelement path="${WL_HOME}/lib/weblogic.jar"/>
</classpath>
</java>
</target>
Listing 5: Client
InitialContext ic = new InitialContext();
Object objRef = ic.lookup( "UserManager" );
UserManagerHome home = (UserManagerHome)
PortableRemoteObject.narrow( objRef, UserManagerHome.class );
UserManager userManager = home.create();
User user = new UserValueObject();
user.setUserName("alex");
user.setUserType("regular");
user.setUserEmail("user@yahoo.com");
userManager.createUser(user);