Advanced CMP Topics: The OrderApp Example

The OrderApp application is an advanced CMP example. It contains entity beans that have self-referential relationships, one-to-one relationships, unidirectional relationships, unknown primary keys, primitive primary key types, and composite primary keys.

Structure of OrderApp

OrderApp is a simple inventory and ordering application for maintaining a catalog of parts and placing an itemized order of those parts. It has entity beans that represent parts, vendors, orders, and line items. These entity beans are accessed using a stateful session bean that holds the business logic of the application. A simple command-line client adds data to the entity beans, manipulates the data, and displays data from the catalog.

The information contained in an order can be divided into different elements. What is the order number? What parts are included in the order? What parts make up that part? Who makes the part? What are the specifications for the part? Are there any schematics for the part? OrderApp is a simplified version of an ordering system that has all these elements.

This example assumes that you have successfully built, assembled, and deployed the RosterApp example application and that you are familiar with assembling entity beans in deploytool.

OrderApp consists of three modules: DataRegistryJAR, an enterprise bean JAR file containing the entity beans, the support classes, and the database schema file; RequestJAR, an enterprise bean JAR containing a stateful session bean that accesses the data in the entity beans; and OrderAppClient, the application client that populates the entity beans with data and manipulates the data, displaying the results in a terminal.

Figure 27-10 shows OrderApp's database tables.

Database Tables in OrderApp

Figure 27-10 Database Tables in OrderApp

Bean Relationships in OrderApp

The RosterApp example application shows how to set up one-to-many and many-to-many relationships between entity beans. OrderApp demonstrates two additional types of entity bean relationships (see Figure 27-11): one-to-one and self-referential relationships.

Relationships between Entity Beans in OrderApp

Figure 27-11 Relationships between Entity Beans in OrderApp

Self-Referential Relationships

A self-referential relationship is a relationship between container-managed relationship fields (CMR) in the same entity bean. PartBean has a CMR field bomPart that has a one-to-many relationship with the CMR field parts, which is also in PartBean. That is, a part can be made up of many parts, and each of those parts has exactly one bill-of-material part.

The primary key for PartBean is a compound primary key, a combination of the partNumber and revision fields. It is mapped to the PART_NUMBER and REVISION columns in the PART table.

One-to-One Relationships

PartBean has a CMR field, vendorPart, that has a one-to-one relationship with VendorPartBean's CMR field part. That is, each part has exactly one vendor part, and vice versa.

One-to-Many Relationship Mapped to Overlapping Primary and Foreign Keys

OrderBean has a CMR field, lineItems, that has a one-to-many relationship with LineItemBean's CMR field order. That is, each order has one or more line item.

LineItemBean uses a compound primary key that is made up of the orderId and itemId fields. This compound primary key maps to the ORDER_ID and ITEM_ID columns in the LINEITEM database table. ORDER_ID is a foreign key to the ORDER_ID column in the ORDERS table. This means that the ORDER_ID column is mapped twice: once as a primary key field, orderId; and again as a relationship field, order.

Unidirectional Relationships

LineItemBean has a CMR field, vendorPart, that has a unidirectional many-to-one relationship with VendorPartBean. That is, there is no CMR field in the target entity bean in this relationship.

Primary Keys in OrderApp's Entity Beans

The OrderApp example uses more complicated primary keys than does RosterApp.

Unknown Primary Keys

In OrderApp, VendorPartBean uses an unknown primary key. That is, the enterprise bean does not specify a primary key field, and uses java.lang.Object as the primary key class.

The LocalVendorPartHome interface's findByPrimaryKey method is defined as follows:

public LocalVendorPart findByPrimaryKey(Object aKey)
  throws FinderException; 

When you package OrderApp in deploytool, you will set the primary key class for VendorPartBean to Unknown Primary Key. deploytool will create the proper entry in the enterprise bean deployment descriptor.

See Generating Primary Key Values for more information on unkown primary keys.

Primitive Type Primary Keys

VendorBean uses a primary key that is a Java programming language primitive type, an int. To use a primitive type as the primary key, you must create a wrapper class. VendorKey is the wrapper class for VendorBean.

The wrapper primary key class has the same requirements as described in The Primary Key Class. This is the VendorKey wrapper class:

package dataregistry;
public final class VendorKey implements java.io.Serializable {

  public int vendorId;

  public boolean equals(Object otherOb) {

    if (this == otherOb) {
      return true;
    }
    if (!(otherOb instanceof VendorKey)) {
      return false;
    }
    VendorKey other = (VendorKey) otherOb;
    return (vendorId == other.vendorId);
  }
  public int hashCode() {
    return vendorId;
  }
  public String toString() {
    return "" + vendorId;
  }
} 

Compound Primary Keys

A compound primary key is made up of multiple fields and follows the requirements described in The Primary Key Class. To use a compound primary key, you must create a wrapper class.

In OrderApp, two entity beans use compound primary keys: PartBean and LineItemBean.

PartBean uses the PartKey wrapper class. PartBean's primary key is a combination of the part number and the revision number. PartKey encapsulates this primary key.

LineItemBean uses the LineItemKey class. LineItemBean's primary key is a combination of the order number and the item number. LineItemKey encapsulates this primary key. This is the LineItemKey compound primary key wrapper class:

package dataregistry;

public final class LineItemKey implements 
      java.io.Serializable {

  public Integer orderId;
  public int itemId;

  public boolean equals(Object otherOb) {
    if (this == otherOb) {
      return true;
    }
    if (!(otherOb instanceof LineItemKey)) {
      return false;
    }
    LineItemKey other = (LineItemKey) otherOb;
    return ((orderId==null?other.orderId==null:orderId.equals
        (other.orderId)) && (itemId == other.itemId));
  }

  public int hashCode() {
    return ((orderId==null?0:orderId.hashCode()) 
        ^ ((int) itemId));
  }

  public String toString() {
    return "" + orderId + "-" + itemId;
  }
} 

Entity Bean Mapped to More Than One Database Table

PartBean's fields map to more than one database table: PART and PART_DETAIL. The PART_DETAIL table holds the specification and schematics for the part. When you set up the container-managed fields and relationships in deploytool, you will add PART_DETAIL as a secondary table for PartBean.

Finder and Selector Methods

VendorBean has two finder methods: findByPartialName and findByOrder. The findByPartialName method searches through the vendor list for matches to a partial name. findByOrder finds all vendors for a particular order.

LineItemBean has one finder method, findAll, which finds all line items.

OrderBean has one selector method, ejbSelectAll, which returns all orders.

VendorPartBean has two selector methods. ejbSelectAvgPrice returns the average price of all parts from a vendor. ejbSelectTotalPricePerVendor returns the price of all the parts from a particular vendor.

Selector methods cannot be accessed outside a bean instance because the selector methods are not defined in the bean interface. If you are using a selector method to return data to a caller, the selector method must be called from a home or business method. In OrderApp, the LocalVendorPartHome.getAvgPrice method returns the result of the ejbSelectAvgPrice method in VendorPartBean.

The return type of a selector query is usually defined by the return type of the ejbSelect methods. You must specify the return type as Remote if the method returns a remote interface or a java.util.Collection of remote interfaces. If the return type is a local interface or a java.util.Collection of local interfaces, set the return type to Local. If the return type is neither a local nor a remote interface, nor a collection of local or remote interfaces, do not set the return type (in deploytool, set the return type to None). The OrderBean.ejbSelectAll method returns a collection of local interfaces. VendorPartBean.ejbSelectAvgPrice and VendorPartBean.ejbSelectTotalPricePerVendor return a Double, so the return type is set to None.

Using Home Methods

Home methods are defined in the home interface of a bean and correspond to methods named ejbHome<METHOD> in the bean class. For example, a method getValue, defined in the LocalExampleHome interface, corresponds to the ejbHomeGetValue method implemented in ExampleBean. The ejbHome<METHOD> methods are implemented by the bean developer.

OrderApp uses three home methods: LocalOrderHome.adjustDiscount, LocalVendorPartHome.getAvgPrice, and LocalVendorPartHome.getTotalPricePerVendor. Home methods operate on all instances of a bean rather than on any particular bean instance. That is, home methods cannot access the container-managed fields and relationships of a bean instance on which the method is called.

For example, LocalOrderHome.adjustDiscount is used to increase or decrease the discount on all orders.

Cascade Deletes in OrderApp

Entity beans that use container-managed relationships often have dependencies on the existence of the other bean in the relationship. For example, a line item is part of an order, and if the order is deleted, then the line item should also be deleted. This is called a cascade delete relationship.

In OrderApp, there are two cascade delete dependencies in the bean relationships. If the OrderBean to which a LineItemBean is related is deleted, then the LineItemBean should also be deleted. If the VendorBean to which a VendorPartBean is related is deleted, then the VendorPartBean should also be deleted.

BLOB and CLOB Database Types in OrderApp

The PART_DETAIL table in the database has a column, DRAWING, of type BLOB. BLOB stands for binary large objects, which are used for storing binary data such as an image. The DRAWING column is mapped to the container-managed field PartBean. drawing of type java.io.Serializable.

PART_DETAIL also has a column, SPECIFICATION, of type CLOB. CLOB stands for character large objects, which are used to store string data too large to be stored in a VARCHAR column. SPECIFICATION is mapped to the container-managed field PartBean.specification of type java.lang.String.


Note: You cannot use a BLOB or CLOB column in the WHERE clause of a finder or selector EJB QL query.


Building and Running the OrderApp Example

This section assumes that you are familiar with how to package entity beans in deploytool as described in Building and Running the RosterApp Example and have created the JDBC resource.


Note: Application Server 8.2 includes a copy of the open source Derby database server. Application Server 8.0/8.1 includes the PointBase database server. If you are using Application Server 8.0/8.1, either follow the instructions in the J2EE Tutorial at http://java.sun.com/j2ee/1.4/docs/tutorial-update6/doc/index.html that works with Application Server 8.0/8.1 or upgrade to Application Server 8.2 (see http://java.sun.com/j2ee/1.4/download.html#appserv to download).


Create the Database Tables

To create the database tables, do the following:

  1. In a terminal navigate to
  2. <INSTALL>/j2eetutorial14/examples/ejb/cmporder/

  3. Enter the following command:
  4. asant create-db_common

Capture the Database Schema

To capture the database schema, do the following:

  1. In a terminal navigate to
  2. <INSTALL>/j2eetutorial14/examples/ejb/cmporder/

  3. Enter the following command:
  4. asant capture-db-schema

Build the Application

To build the application components of OrderApp, do the following:

  1. Navigate to
  2. <INSTALL>/j2eetutorial14/examples/ejb/cmporder/

  3. Enter the following command:
  4. asant build

Package the Application

You will now package the enterprise beans, support classes, database schema, and client class in deploytool. This section assumes that you are familiar with how to package these application modules in deploytool.

Create the Application Modules
  1. Create a new application in deploytool named OrderApp in
  2. <INSTALL>/j2eetutorial14/examples/ejb/cmporder/

  3. Create an enterprise bean JAR named RequestJAR that contains the files in
  4. <INSTALL>/j2eetutorial14/examples/ejb/cmporder/build/
    request/

  5. Set up a stateful session bean, RequestBean, in RequestJAR with a remote home interface of request.RequestHome and a remote interface of request.Request.
  6. Create an enterprise bean JAR named DataRegistryJAR that contains the files in
  7. <INSTALL>/j2eetutorial14/examples/ejb/cmporder/build/
    dataregistry

    And the database schema file:

    <INSTALL>/j2eetutorial14/examples/ejb/cmporder/build/
    cmporder.dbschema

  8. Set up the entity beans (LineItemBean, OrderBean, PartBean, VendorBean, and VendorPartBean) according to Table 27-2 through Table 27-6.
    Table 27-2 Settings for LineItemBean
    Setting
    Value
    Local Home Interface
    dataregistry.LocalLineItemHome
    Local Interface
    dataregistry.LocalLineItem
    Persistent Fields
    orderId, itemId, quantity
    Abstract Schema Name
    LineItem
    Primary Key Class
    User-defined class dataregistry.LineItemKey
    Table 27-3 Settings for OrderBean
    Setting
    Value
    Local Home Interface
    dataregistry.LocalOrderHome
    Local Interface
    dataregistry.LocalOrder
    Persistent Fields
    status, orderId, discount, lastUpdate, shipmentInfo
    Abstract Schema Name
    Order
    Primary Key Class
    Existing field orderId
    Table 27-4 Settings for PartBean
    Setting
    Value
    Local Home Interface
    dataregistry.LocalPartHome
    Local Interface
    dataregistry.LocalPart
    Persistent Fields
    description, partNumber, revision, revisionDate, drawing, specification
    Abstract Schema Name
    Part
    Primary Key Class
    User-defined class dataregistry.PartKey
    Table 27-5 Settings for VendorBean
    Setting
    Value
    Local Home Interface
    dataregistry.LocalVendorHome
    Local Interface
    dataregistry.LocalVendor
    Persistent Fields
    address, name, vendorId, contact, phone
    Abstract Schema Name
    Vendor
    Primary Key Class
    User-defined class dataregistry.VendorKey
    Table 27-6 Settings for VendorPartBean
    Setting
    Value
    Local Home Interface
    dataregistry.LocalVendorPartHome
    Local Interface
    dataregistry.LocalVendorPart
    Persistent Fields
    description, price
    Abstract Schema Name
    VendorPart
    Primary Key Class
    Unknown Primary Key Class
Configure the Entity Bean Relationships

Now we'll configure the relationships of the entity beans and map the fields and relationships to the database tables.

  1. Set up the bean relationships according to Table 27-7:
    Table 27-7 OrderApp Bean Relationships
    Multi-plicity
    Bean A
    Field Referencing Bean B and Field Type
    Delete When Bean B Is Deleted?
    Bean B
    Field Referencing Bean A and Field Type
    Delete When Bean A Is Deleted?
    *:1
    PartBean
    bomPart
     
    PartBean
    parts, java.util.
    Collection
     
    1:*
    OrderBean
    lineItems, java.util.
    Collection
     
    Line
    ItemBean
    order
    Yes
    *:1
    Vendor
    PartBean
    vendor
    Yes
    Vendor
    Bean
    vendorParts, java.util.
    Collection
     
    1:1
    Vendor
    PartBean
    part
     
    PartBean
    vendorPart
     
    *:1
    Line
    ItemBean
    vendorPart
     
    Vendor
    PartBean
    <none>
     
  2. Set the JNDI Name of the CMP Resource to jdbc/ejbTutorialDB.
  3. Create the database mappings using the cmporder.dbschema file in the Sun-specific Settings dialog box, CMP Database view.
  4. Manually map OrderBean to the ORDERS database table in the Sun-specific Settings dialog box, CMP Database view:
    1. Select OrderBean in the Enterprise Bean field under Persistent Field Mappings.
    2. Select ORDERS in the Primary Table drop-down.
    3. ORDER is a reserved keyword in SQL, so the table name is ORDERS.

  5. Map PartBean to the PART and PART_DETAIL database tables:
    1. Select PartBean in the Enterprise Bean field under Persistent Field Mappings.
    2. Click Advanced Settings under Mappings for Bean PartBean.
    3. Click Add.
    4. In the Secondary Table field select PART_DETAIL.
    5. Select PART_NUMBER in the Primary Table Column.
    6. Select PART_NUMBER in the Secondary Table Column.
    7. Click Add Pair.
    8. Select REVISION in the Primary Table Column.
    9. Select REVISION in the Secondary Table Column.
    10. Click OK.
    11. Click OK.
  6. Set the Fetch group of the drawing and specification fields to None.
  7. Click Automap All to automatically map the fields and relationships to the database tables. Repeat this step for all the entity beans until all the relationships and fields are mapped.
  8. Click Close.
Add the Finder and Selector Queries

Add the finder and selector queries to the entity beans as listed in Table 27-8 and Table 27-9:

Table 27-8 Finder Queries in OrderApp
Enterprise Bean
Method
EJB QL Query
VendorBean
findByOrder
SELECT DISTINCT
l.vendorPart.vendor
FROM Order o, IN(o.lineItems) AS l
WHERE o.orderId = ?1 ORDER BY l.vendorPart.vendor.name
VendorBean
findByPartialName
SELECT OBJECT(v) FROM Vendor v
WHERE LOCATE(?1, v.name) > 0
LineItemBean
findAll
SELECT OBJECT(l)
FROM LineItem l

Table 27-9 Selector Queries in OrderApp
Enterprise Bean
Method
EJB QL Query
Return EJB Type
OrderBean
ejbSelectAll
SELECT OBJECT(o)
FROM Order o
Local
VendorPartBean
ejbSelectAvgPrice
SELECT AVG(vp.price)
FROM VendorPart vp
None
VendorPartBean
ejbSelectTotal
PricePerVendor
SELECT SUM(vp.price)
FROM VendorPart vp
WHERE vp.vendor.vendorId = ?1
None


Note: The queries are included in the cmporderQueries.txt file, located in <INSTALL>/j2eetutorial14/examples/ejb/cmporder/ to make it easier to enter the queries.


Set the Transaction Attributes

The transactions for all our enterprise beans (RequestBean, LineItemBean, OrderBean, PartBean, VendorBean, and VendorPartBean) must be managed by the container.

  1. Select the enterprise bean in deploytool.
  2. Select the Transactions tab.
  3. Select Container-Managed under Transaction Management. All transaction attributes for the bean's methods will automatically be set to Required.
Set RequestBean's Enterprise Bean References

RequestBean accesses the local entity beans contained in DataRegistryJAR. You must set the references to the entity beans in RequestBean.

  1. Select RequestBean in RequestJAR.
  2. Click the EJB Ref's tab.
  3. Enter the references according to Table 27-10. All the references are to local entity beans.
    Table 27-10 Enterprise Bean References in RequestBean
    Coded Name
    Home Interface
    Local Interface
    Target Enterprise Bean Name
    ejb/SimpleLineItem
    dataregistry.
    LocalLineItemHome
    dataregistry.
    LocalLineItem
    LineItemBean
    ejb/SimpleVendorPart
    dataregistry.
    LocalVendorPart
    Home
    dataregistry.
    LocalVendorPart
    VendorPartBean
    ejb/SimpleOrder
    dataregistry.
    LocalOrderHome
    dataregistry.
    LocalOrder
    OrderBean
    ejb/SimplePart
    dataregistry.
    LocalPartHome
    dataregistry.
    LocalPart
    PartBean
    ejb/SimpleVendor
    dataregistry.
    LocalVendorHome
    dataregistry.
    LocalVendor
    VendorBean
Package the Application Client

Now we'll add the application client to the EAR.

  1. Create a new application client in OrderApp named OrderAppClient.
  2. Add the contents of the following directory:
  3. <INSTALL>/j2eetutorial14/examples/ejb/cmporder/build/
    client/

  4. Set the main class of the client to client.Client.
  5. Set the enterprise bean reference for the client:
    1. Set the Coded Name to ejb/Request.
    2. Set the EJB Type to Session.
    3. Set the Interfaces to Remote.
    4. Set the Home Interface to request.RequestHome.
    5. Set the Remote Interface to request.Request.
    6. Enter RequestBean in the JNDI Name field under Target EJB.
    7. Click OK.

Deploy the Enterprise Application

OrderApp is now ready to be deployed:

  1. Select FileRight ArrowSave.
  2. Select OrderApp in deploytool.
  3. Select ToolsRight ArrowDeploy.
  4. Check Return Client Jar in the Deploy Module dialog box.

Run the Client Application

The client application accesses the RequestBean session bean, which in turn manipulates data in OrderApp's entity beans.


Note: This example will perform poorly compared with a well-designed CMP application. OrderApp is designed primarily for instructional purposes, and does not follow the best practices recommendations as outlined in the book Designing Enterprise Applications with the J2EETrademarked Platform, Second Edition, Inderjeet Singh et al., (Addison-Wesley, 2002).


To run the client, follow these steps:

  1. In a terminal, go to
  2. <INSTALL>/j2eetutorial14/examples/ejb/cmporder/

  3. Enter the following command:
  4. appclient -client OrderAppClient.jar

  5. You will see the following output in the terminal:
  6. Cost of Bill of Material for PN SDFG-ERTY-BN Rev: 7: $241.86
    Cost of Order 1111: $664.68
    Cost of Order 4312: $2,011.44

    Adding 5% discount
    Cost of Order 1111: $627.75
    Cost of Order 4312: $1,910.87

    Removing 7% discount
    Cost of Order 1111: $679.45
    Cost of Order 4312: $2,011.44

    Average price of all parts: $117.55

    Total price of parts for Vendor 100: $501.06

    Ordered list of vendors for order 1111
    200 Gadget, Inc. Mrs. Smith
    100 WidgetCorp Mr. Jones

    Counting all line items
    Found 6 line items

    Removing Order
    Found 3 line items

    Found 1 out of 2 vendors with 'I' in the name:
    Gadget, Inc.


Note: Re-create the database tables using the create-db_common task before re-running the client.