Prev | Next | J2EETM Developer's Guide
Advanced Topics |
Note: The following material applies only to entity beans with bean-managed persistence. In this release, container-managed persistence does not support table relationships.
storagebin
table might have a one-to-one relationship with a widget
table. This application would model a physical warehouse where each storage bin contains one type of widget and each widget resides in one storage bin.
Figure 9-1 illustrates the storagebin
and widget
tables. Because the storagebinid
uniquely identifies a row in the storagebin
table, it is that table's primary key. The widgetid
is the primary key of the widget
table. The two tables are related because the widgetid
is also a column in the storagebin
table. By referring to the primary key of the widget
table, the widgetid
in the storagebin
table identifies which widget resides in a particular storage bin in the warehouse. Because the widgetid
of the storagebin
table refers to the primary key of another table, it is called a foreign key. (The figure denotes a primary key with PK and a foreign key with FK.)
FIGURE 9-1 One-to-One Table Relationship
A dependent (child) table includes a foreign key that matches the primary key of the referenced (parent) table. The values of the foreign keys in the storagebin
(child) table depend on the primary keys in the widget
(parent) table. For example, if the storagebin
table has a row with a widgetid
of 344, then the widget
table should also have a row whose widgetid
is 344.
When designing a database application, you may choose to enforce the dependency between the parent and child tables. There are two ways to enforce such a dependency: by defining a referential constraint in the database or by performing checks in the application code. The storagebin
table has a referential constraint named fk_widgetid
:
The StorageBinEJBcreate table storagebin (storagebinid varchar(3) constraint pk_storagebin primary key, widgetid varchar(3), quantity integer, constraint fk_widgetid foreign key (widgetid) references widget(widgetid));
and WidgetEJB classes illustrate the one-to-one relationship of the storagebin
and widget
tables. (The source code for the classes is in the doc/guides/ejb/examples/storagebin
directory.)
The StorageEJB
class contains variables for each column in the storagebin
table, including the foreign key, widgetId
:
Theprivate String storageBinId; private String widgetId; private int quantity;
ejbFindByWidgetId
method of the StorageEJB
class returns the storageBinId
that matches a given widgetId
:
Thepublic String ejbFindByWidgetId(String widgetId) throws FinderException { String storageBinId; try { storageBinId = selectByWidgetId(widgetId); } catch (Exception ex) { throw new EJBException("ejbFindByWidgetId: " + ex.getMessage()); } if (storageBinId == null) { throw new ObjectNotFoundException ("Row for widgetId " + widgetId + " not found."); } else { return storageBinId; } }
ejbFindByWidgetId
method locates the widgetId
by querying the database in the selectByWidgetId
method:
To find out which storage bin a widget resides in, the StorageBinClient program calls theprivate String selectByWidgetId(String widgetId) throws SQLException { String storageBinId; String selectStatement = "select storagebinid " + "from storagebin where widgetid = ? "; PreparedStatement prepStmt = con.prepareStatement(selectStatement); prepStmt.setString(1, widgetId); ResultSet rs = prepStmt.executeQuery(); if (rs.next()) { storageBinId = rs.getString(1); } else { storageBinId = null; } prepStmt.close(); return storageBinId; }
findByWidgetId
method:
Tips for running theString widgetId = "777"; StorageBin storageBin = storageBinHome.findByWidgetId(widgetId); String storageBinId = (String)storageBin.getPrimaryKey(); int quantity = storageBin.getQuantity();
StorageBinEJB
example:
cloudTable.sh
(UNIX) or cloudTable.bat
(Windows) script from the command line prompt. For example, you should run the cloudTable.bat
script as follows:cd %J2EE_HOME%\doc\guides\ejb\examples\storagebin ..\util\cloudTable.bat
StorageBinBean
, specify jdbc/StorageBinDB
as the coded name for the resource reference.WidgetBean
, specify jdbc/WidgetDB
as the coded name for the resource reference.
Component/Reference Name
|
JNDI Name
|
---|---|
StorageBinBean | MyStorageBin |
jdbc/StorageBinDB | jdbc/Cloudscape |
WidgetBean | MyWidget |
jdbc/WidgetDB | jdbc/Cloudscape |
team
table and a player
table. Each team has multiple players and each player belongs to a single team. Every row in the child table (player
), has a foreign key identifying the player's team. This foreign key matches the team
table's primary key.
The sections that follow describe how you might implement one-to-many relationships in entity beans. When designing such entity beans, you must decide whether both tables are represented by entity beans, or just one.
FIGURE 9-2 One-to-Many Relationship: Order and Line Items
Not only does a line item belong to an order, it does not exist without the order. Therefore, the lineitems
table should be represented with a helper class and not with an entity bean. Using a helper class in this case is not required, but doing so might improve performance because a helper class uses fewer system resources than an entity bean.
The LineItem and OrderEJB classes show how to implement a one-to-many relationship with a helper class (LineItem
) and an entity bean (OrderEJB
). (The source code for the classes is in the doc/guides/ejb/examples/order
directory.) The instance variables in the LineItem
class correspond to the columns in the lineitems
table. The itemNo
variable matches the primary key for the lineitems
table and the orderId
variable represents the table's foreign key. Here is the source code for the LineItem
class:
Thepublic class LineItem implements java.io.Serializable { String productId; int quantity; double unitPrice; int itemNo; String orderId; public LineItem(String productId, int quantity, double unitPrice, int itemNo, String orderId) { this.productId = productId; this.quantity = quantity; this.unitPrice = unitPrice; this.itemNo = itemNo; this.orderId = orderId; } public String getProductId() { return productId; } public int getQuantity() { return quantity; } public double getUnitPrice() { return unitPrice; } public int getItemNo() { return itemNo; } public String getOrderId() { return orderId; } }
OrderEJB
class contains an ArrayList
variable named lineItems
. Each element in the lineItems
variable is a LineItem
object. The lineItems
variable is passed to the OrderEJB
class in the ejbCreate
method. For every LineItem
object in the lineItems
variable, the ejbCreate
method inserts a row into the lineitems
table. It also inserts a single row into the orders
table. The code for the ejbCreate
method follows:
The OrderClient program creates and loads anpublic String ejbCreate(String orderId, String customerId, String status, double totalPrice, ArrayList lineItems) throws CreateException { try { insertOrder(orderId, customerId, status, totalPrice); for (int i = 0; i < lineItems.size(); i++) { LineItem item = (LineItem)lineItems.get(i); insertItem(item); } } catch (Exception ex) { throw new EJBException("ejbCreate: " + ex.getMessage()); } this.orderId = orderId; this.customerId = customerId; this.status = status; this.totalPrice = totalPrice; this.lineItems = lineItems ; return orderId; }
ArrayList
of LineItem
objects. The program passes this ArrayList
to the entity bean when it invokes the create
method:
Other methods in theArrayList lineItems = new ArrayList(); lineItems.add(new LineItem("p23", 13, 12.00, 1, "123")); lineItems.add(new LineItem("p67", 47, 89.00, 2, "123")); lineItems.add(new LineItem("p11", 28, 41.00, 3, "123")); . . . Order duke = home.create("123", "c44", "open", totalItems(lineItems), lineItems);
OrderEJB
class also access both database tables. The ejbRemove
method, for example, deletes not only a row from the orders
table, but also deletes all corresponding rows in the lineitems
table. The ejbLoad
and ejbStore
methods synchronize the state of an OrderEJB
instance, including the lineItems
ArrayList
, with the orders
and lineitems
tables.
The ejbFindByProductId
method enables clients to locate all orders that have a particular line item. This method queries the lineitems
table for all rows with a particular productId
. The method returns a Collection
of productId
String
objects. The OrderClient
program iterates through the Collection
and prints the primary key of each order:
Tips for running theCollection c = home.findByProductId("p67"); Iterator i=c.iterator(); while (i.hasNext()) { Order order = (Order)i.next(); String id = (String)order.getPrimaryKey(); System.out.println(id); }
OrderEJB
example:
LineItem.class
file to the EJB .jar file that contains the OrderEJB.class
file.jdbc/OrderDB
as the coded name for the resource reference.ejbLoad
before (and ejbStore
after) each business method invocation. These calls will synchronize the bean's state with the database tables.
Component/Reference Name
|
JNDI Name
|
---|---|
OrderBean | MyOrder |
jdbc/OrderDB | jdbc/Cloudscape |
salesrep
table (parent) matches multiple rows in the customer
table (child). Figure 9-4 illustrates this relationship.
FIGURE 9-3 One-to-Many Relationship: Sales Representative and Customers
The SalesRepEJB and CustomerEJB entity bean classes implement the one-to-many relationship of the of
sales
and customer
tables. (The source code for these classes is in the doc/guides/ejb/examples/salesrep
directory.)
The SalesRepEJB
class contains a variable named customerIds
, which is an ArrayList
of String
elements. These String
elements identify which customers belong to the sales representative. Because the customerIds
variable reflects this relationship, the SalesRepEJB
class must keep the variable up to date.
The SalesRepEJB
class instantiates the customerIds
variable in the setEntityContext
method, not in ejbCreate
. The container invokes setEntityContext
just once-- when it creates the bean instance-- ensuring that customerIds
is instantiated just once. Because the same bean instance can assume different identities during its life cycle, instantiating customerIds
in ejbCreate
might cause multiple and unnecessary instantiations. Therefore, the SalesRepEJB
class instantiates the customerIds
variable in setEntityContext
:
Invoked by thepublic void setEntityContext(EntityContext context) { this.context = context; customerIds = new ArrayList(); try { makeConnection(); Context initial = new InitialContext(); Object objref = initial.lookup("java:comp/env/ejb/Customer"); customerHome = (CustomerHome)PortableRemoteObject.narrow(objref, CustomerHome.class); } catch (Exception ex) { throw new EJBException("setEntityContext: " + ex.getMessage()); } }
ejbLoad
method, loadEnrollerIds
is a private method that refreshes the customerIds
variable. There are two approaches when coding a method such as loadCustomerIds
: fetch the identifiers from the customer
database table or get them from the Customer
entity bean. Fetching the identifiers from the database might be faster, but exposes the SalesRepEJB
code to the Customer
bean's underlying database table. In the future, if you were to change the Customer
bean's table (or move the bean to a different J2EE server), then you might need to change the SalesRepEJB
code. But if the SalesRepEJB
gets the identifiers from the Customer
entity bean, no coding changes would be required. The two approaches present a trade-off: performance versus flexibility. The SalesRepEJB
example opts for flexibility, loading the customerIds
variable by calling the findSalesRep
and getPrimaryKey
methods of the Customer
bean. Here is the code for the loadCustomerIds
method:
If a customer's sales representative changes, the client program updates the database by calling theprivate void loadCustomerIds() { customerIds.clear(); try { Collection c = customerHome.findBySalesRep(salesRepId); Iterator i=c.iterator(); while (i.hasNext()) { Customer customer = (Customer)i.next(); String id = (String)customer.getPrimaryKey(); customerIds.add(id); } } catch (Exception ex) { throw new EJBException("Exception in loadCustomerIds: " + ex.getMessage()); } }
setSalesRepId
method of the CustomerEJB
class. The next time a business method of the SalesRepEJB
is called, the ejbLoad
method invokes loadCustomerIds
, which refreshes the customerIds
variable. (To ensure that ejbLoad
is invoked before each business method, set the transaction attributes of the business methods to Required.) For example, the SalesRepClient program changes the salesRepId
for a customer named Mary Jackson:
TheCustomer mary = customerHome.findByPrimaryKey("987"); mary.setSalesRepId("543");
salesRepId
543 identifies a sales representative named Janice Martin. To list all of Janice's customers, the SalesRepClient
program invokes the getCustomerIds
method, iterates through the ArrayList
of identifiers, and locates each Customer
bean by calling the Customer
bean's findByPrimaryKey
method:
Tips for running theSalesRep janice = salesHome.findByPrimaryKey("543"); ArrayList a = janice.getCustomerIds(); i = a.iterator(); while (i.hasNext()) { String customerId = (String)i.next(); Customer customer = customerHome.findByPrimaryKey(customerId); String name = customer.getName(); System.out.println(customerId + ": " + name); }
SalesRepEJB
example:
jdbc/SalesDB
as the coded name for the resource reference.SalesRepEJB
class refers to the Customer
bean, you must specify the EJB reference using the values in the following table.
Dialog Field
|
Value
|
---|---|
Coded Name | ejb/Customer |
Type | Entity |
Home | CustomerHome |
Remote | Customer |
Component/Reference Name
|
JNDI Name
|
---|---|
SalesRepBean | MySalesRep |
CustomerBean | MyCustomer |
jdbc/SalesDB | jdbc/Cloudscape |
ejb/Customer | MyCustomer |
enrollment
table. (PK indicates a primary key and FK a foreign key.)
FIGURE 9-4 Many-to-Many Relationship: Students and Courses
These tables are accessed by the StudentEJB, CourseEJB, and EnrollerEJB classes. (The sample code for these classes is in the doc/guides/ejb/examples/enroller
directory.)
The StudentEJB
and CourseEJB
classes are complementary. Each class contains an ArrayList
of foreign keys. The StudentEJB
class, for example, contains an ArrayList
named courseIds
, which identifies the courses the student is enrolled in. The ejbLoad
method adds elements to the courseIds
ArrayList
by calling loadCourseIds
, a private method. The loadCourseIds
method gets the course identifiers from the Enroller
session bean. The source code for the loadCourseIds
method follows:
Invoked by theprivate void loadCourseIds() { courseIds.clear(); try { Enroller enroller = enrollerHome.create(); ArrayList a = enroller.getCourseIds(studentId); courseIds.addAll(a); } catch (Exception ex) { throw new EJBException("Exception in loadCourseIds: " + ex.getMessage()); } }
loadCourseIds
method, the getCourses
method of the EnrollerEJB
class queries the enrollment
table:
Only theselect courseid from enrollment where studentid = ?
EnrollerEJB
class accesses the enrollment
table. Therefore, the EnrollerEJB
class manages the student-course relationship represented in the enrollment
table. If a student enrolls in a course, for example, the client calls the enroll
business method, which inserts a row:
If a student drops a course, theinsert into enrollment values (studentid, courseid)
unEnroll
method deletes a row:
And if a student leaves the school, thedelete from enrollment where studentid = ? and courseid = ?
deleteStudent
method deletes all rows in the table for that student:
Thedelete from enrollment where student = ?
EnrollerEJB
class does not delete the matching row from the student
table. That action is performed by the ejbRemove
method of the StudentEJB
class. To ensure that both deletes are executed as a single operation, they should belong to the same transaction. (See the Transactions chapter for more information.)
Tips for running the EnrollerEJB
example:
jdbc/CollegeDB
as the coded name for the resource reference.Required
. Student
and Course
entity beans, you must specify an EJB reference to the Enroller
session bean using the values in the following table.
Dialog Field
|
Value
|
---|---|
Coded Name | ejb/Enroller |
Type | Session |
Home | EnrollerHome |
Remote | Enroller |