XA
ASERT LOGO Advanced Software Engineering, Research and Training
Allowing Secure Applications to be Rapidly Developed and Deployed

EJB Container Managed Persistence using WebLogic Server and TopLinkT

Authors: Mark Briggs & Paul King
Last Revised: June 2001

This article discusses the use of WebGain's EJB development tool, TopLink, in conjunction with BEA's Java-based Application Server, WebLogic Server (WLS). Version 3.5 of TopLink was used with Version 6.0 SP2 of WLS.

The article is primarily aimed at Java developers and project managers. Some familiarity with the Java programming language and the J2EE Enterprise JavaBean (EJB) standard is assumed, such as the roles of beans, containers and application servers. It is also assumed that the reader is familiar with the concept of bean deployment using a J2EE-compliant application server such as WLS.


Java is well established in the area of web-based applications, primarily due to the widespread adoption of servlet technology (including JSPs). However it has not necessarily always been the language of choice for implementing Enterprise-level API-based client-server & distributed applications.

Historically, Java lacked a reliable and mature Object Request Broker (ORB) framework. A pure RMI application using the standard registry and activation framework suffered from limited scalability. These limitations were partially overcome using CORBA, but CORBA has several drawbacks:

  • It takes some time to become proficient in the use of CORBA. For example, it is necessary to learn CORBA's Interface Definition Language (IDL) and the corresponding Java language mappings.
  • Most available products use different server-side APIs. Switching to a different product therefore requires the porting of source code. This reduces the Java benefits of cross-platform portability and reduced development time.
  • Many CORBA products do not support an object activation model that is sufficiently scalable or robust.

Java 2 Enterprise Edition (J2EE) has been slowly maturing and evolving to now become the enterprise language of choice for developing distributed corporate and eCommerce applications. Arguably, J2EE's incorporation of two standards lead to the establishment of Java as a principal language for developing servers. These are the EJB and Java Database Connectivity (JDBC) standards.

EJB utilizes RMI to provide a component architecture. It incorporates models for object activation, threading, security, persistence and transactions. An enterprise bean can be deployed using any Application Server that conforms to the EJB standard. It is not necessary to port source code in order to switch to a different new product. In addition, beans are relatively easy to program. Knowledge of Java is required, but the semantic complexity of activation and transactions are left to the container.

JDBC has been used extensively with servlets, and can also be used by EJB applications. Until recently, TP monitors could not co-ordinate global transactions using JDBC. With the release of JDBC 2.0, it is possible for an EJB container to ensure that entity beans in different address spaces are synchronized with their respective databases.


The aim of EJB is to allow developers to concentrate on the business logic, thereby significantly reducing development time. To date, this potential has not been completely realized. The most significant drawback of developing an entity bean (an EJB that represents some persistent data), is that the programmer must supply database access code for all but the simplest of beans. This is called bean-managed persistence, and requires detailed knowledge of the database schema that will be used to make the beans persistent.

EJB also allows for container managed persistence or CMP, whereby the container takes responsibility for managing the persistent state of an entity bean. Products such as WLS provide rudimentary CMP capability but there are problems:

  • EJB products based on the widely adopted EJB 1.1 specification provide a CMP capability which is too basic to be of any use in a real application. For example, it allows mapping from an entity bean to one row in a single relational database table. Alternatively, it can use object serialization, which is equivalent to using file access. It cannot scale well or support any finder query except through primary key lookups.
  • EJB 1.1 also suffers from an application server specific query syntax reducing portability of developed applications.
  • Early product releases also supported quite varying degrees of optimisation (for example, entity bean implementations were notoriously slow initially). Current products provide much better support for optimisations and advanced capabilities such as failover (via clusters). Given the highly competitive J2EE market - numerous product offerings exist ranging from open source through to high-end niche products - we should expect even more improvements in this area during the next phase of J2EE product evolution.
  • The EJB 2.0 specification removes some of EJB 1.1's limitations with support for relationships (allowing for more complex Object-to-Relational mappings) and a common query language as well as a new message-driven bean type. However, support for the soon-to-be-released EJB 2.0 specification will not be stable and mature until at least the end of 2001.

TopLink for BEA WLS is a product from WebGain (acquired from The Object People) that allows for reliable CMP for entity beans. The EJB container delegates to TopLink classes whenever the database must be accessed. Note that the container is still responsible for deciding when database access is required.

TopLink and CMP allow for partitioning of application responsibilities. Use of JDBC can create the same old situation when developers had to write embedded SQL content as well as the program logic. It is often the task of a full-time DBA to create and maintain the database. Bean designers and developers can collaborate with the administrator to provide a mechanism to map entity beans to persistent data.

Mapping of object attributes to relational data can be complex, and this is the problem that TopLink addresses. On the Windows platform, the developer can use a graphical tool to specify mappings from beans to database tables. It also allows the specification of more complex relationship mappings from beans to other beans. These relationships can be programmed using the bean interfaces, but they are made persistent through relation tables and/or foreign keys.

Another problem that TopLink addresses is the implementation of finder methods on home interfaces. A relatively simple finder method may require a fairly complex SQL SELECT statement. TopLink allows finder implementations to be specified using a Java-like expression in the deployment descriptor. This expression maps to a real Java statement that uses TopLink classes to generate the necessary SQL. Alternatively, the developer can write an amendment method in Java or embed an SQL SELECT statement directly into the descriptor.


To see how to use TopLink with WLS, it is beneficial to examine a simple application. We will step through development and deployment of a toy roster system. This application will demonstrate using CMP to create relationships between three different types of bean. These relationships will be consistent, irrespective of which type of bean the client uses. Even a simple system such as this would require a large amount of JDBC coding if bean managed persistence (BMP) were used. However with CMP, the required Java code is minimal.

In our application there are three entity bean types: volunteers, phone numbers, and shifts. Volunteers represent the people who do unpaid work, and phone numbers represent their contact details. Shifts represent the work periods. A volunteer may have zero or more phone numbers, and a phone number may be assigned to zero or more volunteers. This allows for the possibility that more than one person is reachable through the same phone number, for example, family members, etc. Shifts may be associated with zero or more volunteers, so that more than one volunteer can be assigned to the same shift. Very little information is maintained, because the purpose is to show some of the possibilities, rather than provide a usable system.

EJB and home interfaces are listed below.


 package Roster;

 import java.rmi.*;
 import javax.ejb.*;
 import java.util.*;

 public interface PhoneNumber extends EJBObject {

    // return a Vector of Volunteers
    public Vector getVolunteers() throws RemoteException;

    public void addVolunteer(Volunteer volunteer) throws RemoteException;

    public String getNumber() throws RemoteException;
 }
Source for PhoneNumber.java



 package Roster;

 import java.rmi.*;
 import javax.ejb.*;
 import java.util.*;

 public interface PhoneNumberHome extends EJBHome {

    public PhoneNumber create(String number)
                           throws CreateException, RemoteException;

    public PhoneNumber create(String number, Volunteer volunteer)
                           throws CreateException, RemoteException;

    public PhoneNumber findByPrimaryKey(String pk)  // PhoneNumberPK=String
                           throws FinderException, RemoteException;

    public Enumeration findByName(String name)
                           throws FinderException, RemoteException;
 }
Source for PhoneNumberHome.java

Note that the PhoneNumber bean contains an access method to add a volunteer and to retrieve all the volunteers that can be reached using the number. The home interface supports creation of an unassigned number as well as a number associated with a volunteer. There is a finder method that allows all volunteers associated with the number to be retrieved. This situation is mirrored in the volunteer bean.


 package Roster;

 import java.rmi.*;
 import javax.ejb.*;
 import java.util.*;

 public interface Volunteer extends EJBObject {

    public void addPhoneNumber(PhoneNumber number) throws RemoteException;

    // return a Vector of PhoneNumber
    public Vector getPhoneNumbers() throws RemoteException, RosterException;

    public void addShift(Shift shift) throws RemoteException;

    // return a Vector of Shift
    public Vector getShifts() throws RemoteException;

    public void setName(String name) throws RemoteException;

    public String getName() throws RemoteException;
 }
            
Source for Volunteer.java


 package Roster;

 import java.rmi.*;
 import javax.ejb.*;
 import java.util.*;

 public interface VolunteerHome extends EJBHome {

    public Volunteer create(String name)
                         throws CreateException, RemoteException;

    public Volunteer create(String name, PhoneNumber number)
                         throws CreateException, RemoteException;

    public Volunteer findByPrimaryKey(String pk) // VolunteerPK=String
                         throws FinderException, RemoteException;

    public Enumeration findByPhoneNumber(String number)
                         throws FinderException, RemoteException;
 }
            
Source for VolunteerHome.java

The Volunteer bean allows shifts to be assigned and retrieved in addition to phone numbers.


 package Roster;

 import java.rmi.*;
 import javax.ejb.*;
 import java.util.*;

 public interface Shift extends EJBObject {

    public int getID() throws RemoteException;

    public String getDay() throws RemoteException;

    public String getStartTime() throws RemoteException;

    public int getHours() throws RemoteException; 

    // return a Vector of Volunteer
    public Vector getVolunteers() throws RemoteException, RosterException;

    public void addVolunteer(Volunteer volunteer) throws RemoteException;
 }
            
Source for Shift.java


 package Roster;

 import java.rmi.*;
 import javax.ejb.*;
 import java.util.*;

 public interface ShiftHome extends EJBHome {

    public Shift create(int id, String day, String startTime, int hours)
                     throws CreateException, RemoteException;

    public Shift findByPrimaryKey(ShiftPK pk)
                     throws FinderException, RemoteException;

    public Enumeration findByDay(String day)
                           throws FinderException, RemoteException;
 }
            
Source for ShiftHome.java

Shifts consist of days, starting times, and the number of hours required. The home interface is simpler than those for phone numbers and volunteers because there is no finder method to retrieve the shifts associated with a particular volunteer. However, the bean itself still has an access method to do this, providing mirroring of data between volunteers and shifts.

To summarize, there is an N:M relationship between Volunteer and PhoneNumber and between Volunteer and Shift. Beans on either side of a relationship can be retrieved using a bean on the other side. For example, all shifts that a volunteer works can be retrieved from the volunteer, and all volunteers that work a shift can be retrieved from the shift. As is evident, such capabilities allow for true entities, combining the power of a relational database with the ease of use and flexibility of an OO component.

Not only is there no need for the client to know anything about the database schema, there is no need for the developer to have detailed knowledge of the schema, either. Of course, there are still pitfalls, some of which may not be revealed until runtime. This article will allude to some of these, but it is very important that a thorough test plan is implemented before a CMP bean can be deployed in a production environment.


Entity beans can be coded as though they reside exclusively in memory. It is the container that decides when the bean should be loaded and stored, and the container delegates database access to TopLink classes. Naturally, the bean can choose to be informed when it is about to be loaded and stored by providing a suitable implementation of the ejbLoad() and ejbStore() methods inherited from javax.ejb.EntityBean. With CMP, these methods usually have an empty implementation.

In the Roster example, beans are related through their remote interfaces. This is the natural way to relate beans to each other and is necessary for TopLink persistence to function correctly. Of course, beans cannot be directly related. Holding a direct reference to another bean would violate the EJB model and by-pass the container mechanisms. In any case there is no standard way for a non-container class to obtain a direct reference to any bean. Therefore object references cannot be used in the same manner that a non-distributed application can use them.

Code for class VolunteerBean is reproduced below. The other beans contain similar logic.


 package Roster;

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

 import TOPLink.Public.Indirection.*;
 import TOPLink.Public.PublicInterface.*;

 public class VolunteerBean extends GenericBean {
    private ValueHolderInterface contactNumbers, shifts;
    public String name;

    public VolunteerBean() {
        super();
        contactNumbers = new ValueHolder(new Vector());
        shifts = new ValueHolder(new Vector());
        name = null;
    }

    public String ejbCreate() {  // VolunteerPK=String
        return null;
    }
    
    public String ejbCreate(String name) {
        this.name = name;
        return null;
    }

    public void ejbPostCreate(String name) {}

    public String ejbCreate(String name, PhoneNumber phoneNumber) {
                          
        this.name = name;
        ((Vector)contactNumbers.getValue()).addElement(phoneNumber);
        return null;
    }

    public void ejbPostCreate(String name, PhoneNumber phoneNumber) 
                        throws java.rmi.RemoteException {
        // ensure consistency
        phoneNumber.addVolunteer((Volunteer)ctx.getEJBObject());
    }

    public void addPhoneNumber(PhoneNumber number) 
                              throws java.rmi.RemoteException {
        Vector v = (Vector)contactNumbers.getValue();
        if (!v.contains(number)) {
            v.addElement(number);
            // ensure consistency
            number.addVolunteer((Volunteer)ctx.getEJBObject());
        }
    };

    public Vector getPhoneNumbers() throws RosterException {
        Vector v = (Vector)contactNumbers.getValue();
        if (v.size() == 0) throw new RosterException("No Contact Number");
        return v; 
    }

    public void addShift(Shift shift) throws java.rmi.RemoteException {
        Vector v = (Vector)shifts.getValue();
        if (!v.contains(shift)) {
            v.addElement(shift);
            // ensure consistency
            shift.addVolunteer((Volunteer)ctx.getEJBObject());
        }
    }

    public Vector getShifts() {
        return (Vector)shifts.getValue();
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

 }
            
Source for VolunteerBean.java

The above implementation is only for illustrative purposes and does not properly conform to the EJB model. For example, it uses vectors for its collections, where the vectors contain remote references. The method java.util.Vector.contains() uses the Object.equals() method to test for equality, but this is an invalid way of testing whether two instances of a remote interface refer to the same bean. Instead, javax.ejb.EJBObject.isIdentical() should be used. TopLink provides helper classes to assist with achieving the correct semantics but they haven't been shown here.

Additionally, it may be better to interrogate the remote interface about its contained objects, which would require additional methods to be defined. Here the addPhoneNumber() and addShift() methods assume that if one side of a relationship is set, the other side must already be set, since the other bean already exists. This is a valid assumption, since a call is always made to the bean on the other side of the relationship when the relationship does not yet exist. Such schemes are justified when a team collaborates on the application, but cannot be applied when one bean has no knowledge of the other's implementation. In addition, such schemes may make maintenance more problematic.

To ensure consistency, ejbPostCreate() must be used to call the reflexive bean after object creation. This is not possible during ejbCreate() since the container will not set the remote object in the EntityContext until the bean has been actually been created without error. The most common problem is that a bean with the same primary key already exists in the database.

There is one more point to note, and that is the use of class ValueHolder. This is a TopLink class that provides indirection and is a form of weak reference. A value holder stores information that allows it to retrieve the object it references from the database. However, if the attribute is not used the object it refers to will not be loaded. This is a very useful feature, especially for applications that contain references to collections, such as this.


The first stage of development involves devising the interfaces and coding the EJB classes. When that is accomplished, a schema must be produced for the data model. Then the database must be created followed by the creation of all relevant tables. It will be necessary to alter table definitions after creation when foreign keys are needed for circular dependencies.

Table definitions for the Roster application are shown below.

Table Phone_Number: primary table for bean PhoneNumber
Field P_Num char(20): maps to attribute number
Table Volunteer: primary table for bean Volunteer
Field V_Name char(20): maps to attribute name
Table Shift: primary table for bean Shift
Field S_Time char(20): maps to attribute time
Field S_Day char(20): maps to attribute day
Field S_Hours Integer: maps to attribute hours
Field S_ID Integer: maps to attribute ID
Table Volunteer_Phone: relation table
Field V_Name char(20): foreign key to Volunteer.V_Name
Field P_Num char(20): foreign key to Phone_Number.P_Num
Table Volunteer_Shift: relation table
Field V_Name char(20): foreign key to Volunteer.V_Name
Field S_ID Integer: foreign key to Shift.S_ID
Table Phone_Volunteer: relation table
Field P_Num char(20): foreign key to Phone_Number.P_Num
Field V_Name char(20): foreign key to Volunteer.V_Name
Table Shift_Volunteer: relation table
Field S_ID Integer: foreign key to Shift.S_ID
Field V_Name char(20): foreign key to Volunteer.V_Name
Table Definitions

Tables Shift_Volunteer and Phone_Volunteer are not really necessary. They have been indexed on the foreign keys to Shift.ID and Phone_Number.PhoneNum, respectively. Table Volunteer_Phone and Volunteer_Shift are both indexed on the foreign key for Volunteer.Name. This redundancy simply speeds retrievals during finder operations. The method of using reciprocal calls on the relevant beans ensures that the relation tables are consistent with each other.

Relation tables must allow duplicate indexed keys because the relationships are N:M. If the table did not allow duplicates, the DBMS would not allow every insert and would throw a general SQL exception. Another point to note is that it would be far more efficient to use sequenced primary keys for the data tables. Relation tables would reference the sequenced key rather than the actual data; in this case the character fields. However, some JDBC drivers will not support such features, and may throw an SQL exception with a message such as "Driver Not Capable".



TopLink comes with a java-based Mapping Workbench tool that allows the CMP configuration for your application to be entered and manipulated. TopLink Mapping Workbench is based around the idea of projects which are comprised of several elements, including:
  • Database connection information, such as the URL, JDBC driver, and login parameters
  • Table schema details
  • EJB class details
  • Mapping information

Before beginning, it is necessary to ensure that TopLink is installed and registered as a persistance management type for WebLogic Server.

The remainder of this section outlines how the TopLink Mapping Workbench was used to complete our tutorial example as described in earlier sections.

First, our Bean class files from above (PhoneNumberBean, VolunteerBean, ShiftBean) were imported. In the screen snapshot below, these are shown in the explorer-like pane on the left. The right-hand pane displays various kinds of property information.

Then, we defined our database configuration in terms of drivers & URLs (and in general, also usernames & passwords). The tool supports different defined logins which are really just different database configurations. This is useful a feature which, among other things, allows you to define different database configurations, e.g. for development/testing/staging and production. In many cases with WLS, our deployment login will utilise a connection to an appropriate WLS connection pool at runtime but during development we may interact with the database directly.

Next, database meta-information must be retrieved, by connecting to the database. Once connected, we can then select which tables from the database we would like to map against. In our example, all the tables are selected. TopLink also supports certain features for creating tables on the fly but we don't discuss those features here.

Once we have both bean information and table information in TopLink we can start mapping the two together. For the roster application, each bean will be primarily associated with one table. The snapshot below shows the ShiftBean being associated with the SHIFT table. Note that S_ID has been selected as the primary key.



Before jumping into more mapping details, it is worth noting some additional information we can view via the TopLink Mapping Workbench's main window. Several screenshots illustrate some property information for the roster application.

Firstly, class information for the VolunteerBean.

Then, attribute information. Note that contactNumbers and shifts are declared private. These could have been made public, however, in this example we have left them private and we need to ensure that our weblogic.policy file grants the generated TopLink class files sufficient privileges to view private variables via reflection. Something like the following would suffice:

Permission java.lang.reflect.ReflectPermission "suppressAccessChecks";

Finally, we can also view method information.

Now, we want to explore mapping information for our roster application in more detail.

In the left pane you can see the various attributes (or instance variables) within each bean. Because these beans are very simple, they primarily map to only one table each. In most cases, our attributes map directly to a particular field within the corresponding database. It is also possible to have direct mappings from attributes to fields in more than one table. For attributes that do not map easily to database field types, conversions can be carried out using transformation mappings. It is even possible to serialize an object to a table field, as long as the database supports BLOB fields. Highlighted is the day attribute for ShiftBean. It maps directly to the S_DAY field within the SHIFT database. Note the Direct-To-Field Mapping Icon used to signify this type of mapping.

It is in the ability to set relationship mappings that the real power of TopLink becomes evident. Relationships are achieved by mapping beans to other beans through relation tables. In the Roster example, collections of beans are used for N:M relationships, but it is also possible to create 1:1 and 1:N relationships. These can be simpler than N:M relationships because a relation table won't always be required. Instead, a foreign key can be used to refer directly to a row in another (or the same) data table.

In the roster example, there is a many to many relationship between shifts and volunteers. This is captured by the volunteers attribute which is shown here using the Many-To-Many Mapping symbol. In this example, we have used a value holder helper class to refer to the collection corresponding to this mapping. We have also identified the special relation table SHIFT_VOLUNTEER.

We also specify the correspondence between the fields in the source, target and relation tables.

Whenever the instance variable volunteers is altered, an appropriate record will be inserted into table Volunteer_Shift. Recall that the variable volunteers is of type ValueHolder, and that the value is a java.util.Vector containing EJB Shift interfaces. When a client attempts to add a relationship, the bean checks that the relationship does not already exist by checking its container. If the container does not need to be altered, it is also unnecessary to update the database. Although the EJB container may call ejbStore() at the completion of every method, TopLink uses the indirection object to determine whether the underlying value actually needs to be saved. Thus, the elimination of redundancy in the entity bean aids in the elimination of redundancy in the database.

Although not needed for this example, TopLink supports a rich set of mappings. These can be seen from the Map As menu selection shown in the screen snapshot below.

It is also possible to set advanced mapping properties, some of which can be seen below.

Once the mappings have been done, two steps remain. Firstly, a project Java source file is created (or exported) from within the File menu. This creates some Java source code specific to the project which among other things establishes the connections to the various database tables required within the mappings associated with this project. Secondly, a deployment descriptor XML file is created for each bean. These will be included along with the standard ejb-jar.xml and weblogic-ejb-jar.xml files in the jar for this EJB application.


J2EE application servers use XML deployment descriptors to capture configuration information. Shown below are the ejb-jar.xml (generic information) and weblogic-ejb-jar.xml (WLS server specific information) files.

The ejb-jar.xml describes the classes which make up each bean and, in this case, our transactional requirements.


 <?xml version="1.0"?>
<!DOCTYPE ejb-jar PUBLIC "-//Sun Microsystems, Inc.//DTD Enterprise JavaBeans 1.1//EN"
"http://java.sun.com/j2ee/dtds/ejb-jar_1_1.dtd">
<ejb-jar>
<enterprise-beans>
<entity>
<ejb-name>PhoneNumberName</ejb-name>
<home>Roster.PhoneNumberHome</home>
<remote>Roster.PhoneNumber</remote>
<ejb-class>Roster.PhoneNumberBean</ejb-class>
<persistence-type>Container</persistence-type>
<prim-key-class>java.lang.String</prim-key-class>
<reentrant>False</reentrant>
<cmp-field>
<field-name>number</field-name>
</cmp-field>
<primkey-field>number</primkey-field>
</entity>
<entity>
<ejb-name>VolunteerName</ejb-name>
<home>Roster.VolunteerHome</home>
<remote>Roster.Volunteer</remote>
<ejb-class>Roster.VolunteerBean</ejb-class>
<persistence-type>Container</persistence-type>
<prim-key-class>java.lang.String</prim-key-class>
<reentrant>False</reentrant>
<cmp-field>
<field-name>name</field-name>
</cmp-field>
<primkey-field>name</primkey-field>
</entity>
<entity>
<ejb-name>ShiftName</ejb-name>
<home>Roster.ShiftHome</home>
<remote>Roster.Shift</remote>
<ejb-class>Roster.ShiftBean</ejb-class>
<persistence-type>Container</persistence-type>
<prim-key-class>Roster.ShiftPK</prim-key-class>
<reentrant>False</reentrant>
<cmp-field>
<field-name>id</field-name>
</cmp-field>
</entity>
</enterprise-beans>
<assembly-descriptor>
<container-transaction>
<method>
<ejb-name>PhoneNumberName</ejb-name>
<method-name>*</method-name>
</method>
<trans-attribute>Required</trans-attribute>
</container-transaction>
<container-transaction>
<method>
<ejb-name>VolunteerName</ejb-name>
<method-name>*</method-name>
</method>
<trans-attribute>Required</trans-attribute>
</container-transaction>
<container-transaction>
<method>
<ejb-name>ShiftName</ejb-name>
<method-name>*</method-name>
</method>
<trans-attribute>Required</trans-attribute>
</container-transaction>
</assembly-descriptor>
</ejb-jar>
ejb-jar.xml

 

The weblogic-ejb-jar.xml file describes WLS-specific configuration information. In our case, we specify the JNDI names for our beans and we indicate that we are using TopLink as the persistence type.


 <?xml version="1.0"?>
 <!DOCTYPE weblogic-ejb-jar PUBLIC "-//BEA Systems, Inc.//DTD WebLogic 6.0.0 EJB//EN"
             "http://www.bea.com/servers/wls600/dtd/weblogic-ejb-jar.dtd">
 <weblogic-ejb-jar>
     <weblogic-enterprise-bean>
         <ejb-name>PhoneNumberName</ejb-name>
         <entity-descriptor>
		     <persistence>
		         <persistence-type>
				     <type-identifier>TOPLink_CMP</type-identifier>
				   	 <type-version>3.5</type-version>
					 <type-storage>META-INF/toplink-cmp-phone.xml</type-storage>
			 	 </persistence-type>
				 <persistence-use>
					 <type-identifier>TOPLink_CMP</type-identifier>
					 <type-version>3.5</type-version>
				 </persistence-use>
			 </persistence>
		 </entity-descriptor>
		 <enable-call-by-reference>True</enable-call-by-reference>
         <jndi-name>toplink.PhoneNumberHome</jndi-name>
     </weblogic-enterprise-bean>
     <weblogic-enterprise-bean>
<ejb-name>VolunteerName</ejb-name>
<entity-descriptor>
<persistence>
<persistence-type>
<type-identifier>TOPLink_CMP</type-identifier>
<type-version>3.5</type-version>
<type-storage>META-INF/toplink-cmp-volunteer.xml</type-storage>
</persistence-type>
<persistence-use>
<type-identifier>TOPLink_CMP</type-identifier>
<type-version>3.5</type-version>
</persistence-use>
</persistence>
</entity-descriptor>
<enable-call-by-reference>True</enable-call-by-reference>
<jndi-name>toplink.VolunteerHome</jndi-name>
</weblogic-enterprise-bean>
<weblogic-enterprise-bean>
<ejb-name>ShiftName</ejb-name>
<entity-descriptor>
<persistence>
<persistence-type>
<type-identifier>TOPLink_CMP</type-identifier>
<type-version>3.5</type-version>
<type-storage>META-INF/toplink-cmp-shift.xml</type-storage>
</persistence-type>
<persistence-use>
<type-identifier>TOPLink_CMP</type-identifier>
<type-version>3.5</type-version>
</persistence-use>
</persistence>
</entity-descriptor>
<enable-call-by-reference>True</enable-call-by-reference>
<jndi-name>toplink.ShiftHome</jndi-name>
</weblogic-enterprise-bean>
</weblogic-ejb-jar>
weblogic-ejb-jar.xml

 

TopLink generates one additional xml file per bean which specifies the helper class file previously generated and any finder methods. Finder methods declared in the home interface must have a finder specification with matching signatures. Various approaches are possible to specify finders. In this example, we have used a Java-like expression which will be converted into an actual Java expression that will generate the SQL selects, using the attribute variables and associated mappings in the entity bean.

For the roster application, we have shown the toplink-cmp-volunteer.xml file which has a findByPhoneNumber() finder method. Recall that this finder returns a java.util.Enumeration. The Java-like finder expression uses the anyOf() and get() methods. The former can be used with collections such as the phoneNumbers vector, while the latter is used with single objects. Instance variables of the accessed objects must be quoted. Parameter replacement is used for unquoted references; in this case there is only one parameter, aNumber. In this case, all Volunteer interfaces will be returned whose collection of PhoneNumber contains a single PhoneNumber object whose number attribute matches the finder parameter. Note that although VolunteerBean has a collection of remote interfaces, the actual attribute is obtained from the underlying PhoneNumberBean object, through TopLink project information.

				  
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE toplink-cmp-bean PUBLIC '-//WebGain, Inc.//DTD 
TopLink for WebLogic CMP 3.5//EN' 'http://www.webgain.com/Products/toplink/tlwl/toplink-cmp_3_5.dtd'>
<toplink-cmp-bean>
  <project>
    <project-class>Roster.RosterProject</project-class>
    <project-identifier>RosterProjID</project-identifier>
    <project-pool-name>rosterPool</project-pool-name>
    <project-options>
      <log-messages>true</log-messages>
    </project-options>
  </project>
  <finder-list>
    <finder>
      <method-name>findByPhoneNumber</method-name>
      <method-params>
        <method-param>
          <param-type>java.lang.String</param-type>
          <param-name>aNumber</param-name>
        </method-param>
      </method-params>
      <finder-type>EXPRESSION</finder-type>
      <finder-query>
        <![CDATA[builder.anyOf("contactNumbers").get("number").equal(aNumber);]]>
      </finder-query>
    </finder>
  </finder-list>
</toplink-cmp-bean>

            
toplink-cmp-volunteer.xml

Given that we have two other beans, we will also have two additional xml files for this example: toplink-cmp-phone.xml and toplink-cmp-shift.xml. We place all of the XML files in the META-INF directory of our jar'd application.


We have now created all the necessary files for our application. We now need to generate the container classes, package all the classes together and deploy our application. We used the following script (created for Windows 2000) to achieve this.

 


@REM === Adjust these variables to where you installed WebLogic and TOPLink
set JAVA_HOME=c:\devtools\bea\jdk130
set WL_HOME=c:\devtools\bea\wls6
set TL_HOME=c:\devtools\TopLink
set TARGETDOMAIN=%WL_HOME%\config\TopLink_Domain


@REM === Set the classpath to include the WebLogic and TOPLink jars
set WEBLOGIC=%WL_HOME%\lib\weblogic.jar
set TL_LIB=%TL_HOME%\TLJ\Classes\JDK1.3
set TLWL_LIB=%TL_HOME%\TLWL\Classes\JDK1.3
set TOPLINK=%TL_LIB%\toplink.jar;%TL_LIB%\tools.jar;%TL_LIB%\toplinkx.jar;%TLWL_LIB%\toplinkwlx.jar
set CLOUD_HM=%WL_HOME%\samples\eval\cloudscape35
set DATABASE=%CLOUD_HM%\lib\cloudscape.jar
set MYCLASSPATH=%TOPLINK%;%WEBLOGIC%;%DATABASE%
					
@REM === Create a temporary build dir and copy the XML deployment descriptors into it
xcopy *.xml build\META-INF /Q /I


@REM === Compile the (server-side) ejb classes into the build dir
%JAVA_HOME%\bin\javac -d build -classpath %MYCLASSPATH% GenericBean.java 
                    PhoneNumber.java PhoneNumberBean.java PhoneNumberHome.java 
                    RosterProject.java Volunteer.java VolunteerBean.java VolunteerHome.java 
                    Shift.java ShiftHome.java ShiftBean.java ShiftPK.java RosterException.java


@REM === Create a standard jar file that includes the classes and XML deployment descriptors
set STD_JAR=std_Roster.jar
pushd build
%JAVA_HOME%\bin\jar c0f %STD_JAR% META-INF Roster
rmdir Roster /S /Q
popd
					
@REM === Run ejbc to create the deployable EJB jar file from the standard one
set EJB_JAR=ejb_Roster.jar
%JAVA_HOME%\bin\java -classpath %MYCLASSPATH%;.\build -Dweblogic.home=%WL_HOME% 
                    weblogic.ejbc -compiler javac
                    build\%STD_JAR%
                    %TARGETDOMAIN%\applications\%EJB_JAR%
					
@REM === Use javac to copy the client side EJB interfaces into the clientclasses directory
%JAVA_HOME%\bin\javac -classpath %MYCLASSPATH%;.\build -d %TARGETDOMAIN%\clientclasses
                    RosterClient.java PhoneNumber.java 
                    PhoneNumberHome.java Volunteer.java VolunteerHome.java Shift.java 
                    ShiftHome.java ShiftPK.java RosterException.java
					
@REM === Clean up
rmdir build /S /Q
                 
buildAll.cmd script

From looking at the above script, you will notice that the script compiles the source files and packages them into a preliminary jar file before invoking WebLogic's EJB Compiler tool (ejbc). Because TopLink is registered as a persistence type for WebLogic, invoking ejbc will result in the appropriate WebLogic and TopLink generated container classes. The following winzip screenshot displays the resulting file.

The build script places this resulting file into the configured domain's application directory from where it will be automatically loaded into the application server. Note in the jar file, all the original XML deployment descriptors, the serialized deployment descriptors, the stub and skeleton classes, the project class, the normal generated code for entity beans and the TopLink specific files for each bean class.


Below is a fragment of a Roster application client to create some beans.


 ...
 public void createEntities() throws Exception {
       System.out.println("Acquiring Transaction Context");
       UserTransaction tx = 
              (UserTransaction)ic.lookup("javax.transaction.UserTransaction");
       tx.begin();
       System.out.println("Lookup VolunteerHome");
       vh = (VolunteerHome)ic.lookup("toplink.VolunteerHome");
       System.out.println("Lookup PhoneNumberHome");
       ph = (PhoneNumberHome)ic.lookup("toplink.PhoneNumberHome");
       persons[0] = vh.create("Max Winter");
       phones[0] = ph.create("333 4343");
       persons[1] = vh.create("Maxine Winter", phones[0]); 
       persons[2] = vh.create("Carl Carlyle");
       phones[1] = ph.create("555 4545", persons[2]);
       phones[2] = ph.create("666 4646");
       System.out.println("Lookup ShiftHome");
       sh = (ShiftHome)ic.lookup("toplink.ShiftHome");
       shifts[0] = sh.create(1, "MONDAY", "16:00", 5); 
       shifts[1] = sh.create(2, "TUESDAY", "9:00", 8); 
       shifts[2] = sh.create(3, "SUNDAY", "10:00", 3); 
       shifts[3] = sh.create(4, "SUNDAY", "14:00", 6);
       tx.commit();
       System.out.println("Entity Beans Created");
 }
 ...
Source Fragment from RosterClient.java

After the entities have been created, they are modified and then information is retrieved. The output from a sample run is shown below.

Entity Beans Created
Properties Set
Max Winter contact numbers:
333 4343, 666 4646
: shifts:
MONDAY, 16:00 SUNDAY, 14:00
Maxine Winter contact numbers:
333 4343, 666 4646,
: shifts:
MONDAY, 16:00
Carl Carlyle contact numbers:
555 4545
: shifts:
TUESDAY, 9:00 SUNDAY, 10:00

333 4343 reaches :
Maxine Winter, Max Winter
555 4545 reaches :
Carl Carlyle
666 4646 reaches :
Max Winter, Maxine Winter

MONDAY, 16:00 assigned to:
Max Winter, Maxine Winter
TUESDAY, 9:00 assigned to:
Carl Carlyle
SUNDAY, 10:00 assigned to:
Carl Carlyle
SUNDAY, 14:00 assigned to:
Max Winter

Found 333 4343
Found for number: 333 4343
Max Winter, Maxine Winter
Found for name: Carl Carlyle
555 4545
Found for day: SUNDAY
SUNDAY, 10:00: SUNDAY, 14:00:

Phone numbers for non-existent name: RUDOLF
Found nothing


With TopLink it is now possible to build CMP entity beans for most projects regardless of the underlying bean design & database schema. Usually, development time will be significantly less than it would be if BMP were used. Naturally, developers do have to spend some time learning how to use the TopLink tools and classes. TopLink makes it easy to partition the application between different developers and teams, depending on their expertise, for example, between Java programmers and DBAs. Although the need for database knowledge is reduced, all developers still need a basic understanding of the data model.

Developers still have to give some thought to database consistency issues, and to the capabilities of the DBMS and JDBC drivers. For example, code for the javax.ejb.EnityBean.ejbRemove() method will be generated automatically but may not be correct. TopLink will issue an SQL delete command to remove a row in a table (or tables) that the entity has been mapped to, but it cannot ensure this would not cause problems. For example, it could leave stale rows in one or more relation tables. A DBMS may reject such a request or the JDBC driver may be unable to handle it, and an SQL exception would be thrown, rather than a RemoveException. Other issues such as this will certainly arise in complex applications, and it is the responsibility of designers to be aware of them. Even when complex issues do arise, TopLink allows them to be solved using Java programming techniques rather than by coding SQL requests in Java source code. This is another step toward the goal of toward building genuine 3-tier applications with EJB.