Download
FAQ History |
API
Search Feedback |
Mapping Table Relationships for Bean-Managed Persistence
In a relational database, tables can be related by common columns. The relationships between the tables affect the design of their corresponding entity beans. The entity beans discussed in this section are backed up by tables with the following types of relationships:
One-to-One Relationships
In a one-to-one relationship, each row in a table is related to a single row in another table. For example, in a warehouse application, a
storagebin
table might have a one-to-one relationship with awidget
table. This application would model a physical warehouse in which each storage bin contains one type of widget and each widget resides in one storage bin.Figure 26-1 illustrates the
storagebin
andwidget
tables. Because thestoragebinid
uniquely identifies a row in thestoragebin
table, it is that table's primary key. Thewidgetid
is the primary key of thewidget
table. The two tables are related because thewidgetid
is also a column in thestoragebin
table. By referring to the primary key of thewidget
table, thewidgetid
in thestoragebin
table identifies which widget resides in a particular storage bin in the warehouse. Because thewidgetid
of thestoragebin
table refers to the primary key of another table, it is called a foreign key. (The figures in this chapter denote a primary key with PK and a foreign key with FK.)
Figure 26-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 thewidget
(parent) table. For example, if thestoragebin
table has a row with awidgetid
of 344, then the widget table should also have a row whosewidgetid
is 344.When designing a database application, you can 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 namedfk_widgetid
:CREATE TABLE storagebin (storagebinid VARCHAR(3) CONSTRAINT pk_storagebin PRIMARY KEY, widgetid VARCHAR(3), quantity INTEGER, CONSTRAINT fk_widgetid FOREIGN KEY (widgetid) REFERENCES widget(widgetid));The source code for the following example is in this directory:
The
StorageBinBean
andWidgetBean
classes illustrate the one-to-one relationship of thestoragebin
andwidget
tables. TheStorageBinBean
class contains variables for each column in thestoragebin
table, including the foreign key,widgetId
:The
ejbFindByWidgetId
method of theStorageBinBean
class returns thestorageBinId
that matches a givenwidgetId
:public 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; } }The
ejbFindByWidgetId
method locates thewidgetId
by querying the database in theselectByWidgetId
method:private String selectByWidgetId(String widgetId) throws SQLException { String storageBinId; makeConnection(); 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(); releaseConnection(); return storageBinId; }To find out in which storage bin a widget resides, the
StorageBinClient
program calls thefindByWidgetId
method:String widgetId = "777"; StorageBin storageBin = storageBinHome.findByWidgetId(widgetId); String storageBinId = (String)storageBin.getPrimaryKey(); int quantity = storageBin.getQuantity();Running the StorageBinBean Example
One-to-Many Relationships
If the primary key in a parent table matches multiple foreign keys in a child table, then the relationship is one-to-many. This relationship is common in database applications. For example, an application for a sports league might access a
team
table and aplayer
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 theteam
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 only one.
A Helper Class for the Child Table
Not every database table needs to be mapped to an entity bean. If a database table doesn't represent a business entity, or if it stores information that is contained in another entity, then you should use a helper class to represent the table. In an online shopping application, for example, each order submitted by a customer can have multiple line items. The application stores the information in the database tables shown by Figure 26-2.
Figure 26-2 One-to-Many Relationship: Order and Line Items
Not only does a line item belong to an order, but it also 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 does an entity bean.The source code for the following example is in this directory:
The
LineItem
andOrderBean
classes show how to implement a one-to-many relationship using a helper class (LineItem
). The instance variables in theLineItem
class correspond to the columns in thelineitems
table. TheitemNo
variable matches the primary key for thelineitems
table, and theorderId
variable represents the table's foreign key. Here is the source code for theLineItem
class:public 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; } }The
OrderBean
class contains anArrayList
variable namedlineItems
. Each element in thelineItems
variable is aLineItem
object. ThelineItems
variable is passed to theOrderBean
class in theejbCreate
method. For everyLineItem
object in thelineItems
variable, theejbCreate
method inserts a row into thelineitems
table. It also inserts a single row into theorders
table. The code for theejbCreate
method follows:public 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; }The
OrderClient
program creates and loads anArrayList
ofLineItem
objects. The program passes thisArrayList
to the entity bean when it invokes thecreate
method:ArrayList 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);Other methods in the
OrderBean
class also access both database tables. TheejbRemove
method, for example, not only deletes a row from theorders
table but also deletes all corresponding rows in thelineitems
table. TheejbLoad
andejbStore
methods synchronize the state of anOrderBean
instance, including thelineItems
ArrayList
, with theorders
andlineitems
tables.The
ejbFindByProductId
method enables clients to locate all orders that have a particular product. This method queries thelineitems
table for all rows with a specificproductId
. The method returns aCollection
ofOrder
objects. TheOrderClient
program iterates through theCollection
and prints the primary key of each order:Collection 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); }Running the OrderBean Example
An Entity Bean for the Child Table
You should consider building an entity bean for a child table under the following conditions:
These conditions exist in the following scenario. Suppose that each sales representative in a company has multiple customers and that each customer has only one sales representative. The company tracks its sales force using a database application. In the database, each row in the
salesrep
table (parent) matches multiple rows in thecustomer
table (child). Figure 26-3 illustrates this relationship.
Figure 26-3 One-to-Many Relationship: Sales Representative and Customers
The
SalesRepBean
andCustomerBean
entity bean classes implement the one-to-many relationship of thesales
andcustomer
tables.The source code for this example is in this directory:
The
SalesRepBean
class contains a variable namedcustomerIds
, which is anArrayList
ofString
elements. TheseString
elements identify which customers belong to the sales representative. Because thecustomerIds
variable reflects this relationship, theSalesRepBean
class must keep the variable up-to-date.The
SalesRepBean
class instantiates thecustomerIds
variable in thesetEntityContext
method and not inejbCreate
. The container invokessetEntityContext
only once--when it creates the bean instance--thereby ensuring thatcustomerIds
is instantiated only once. Because the same bean instance can assume different identities during its life cycle, instantiatingcustomerIds
inejbCreate
might cause multiple and unnecessary instantiations. Therefore, theSalesRepBean
class instantiates thecustomerIds
variable insetEntityContext
:public void setEntityContext(EntityContext context) { this.context = context; customerIds = new ArrayList(); try { 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()); } }Invoked by the
ejbLoad
method,loadCustomerIds
is a private method that refreshes thecustomerIds
variable. There are two approaches to coding a method such asloadCustomerIds
: fetch the identifiers from thecustomer
database table, or get them from theCustomerBean
entity bean. Fetching the identifiers from the database might be faster, but it exposes the code in theSalesRepBean
class to theCustomerBean
bean's underlying database table. In the future, if you were to change theCustomerBean
bean's table (or move the bean to a different Application Server), you might need to change theSalesRepBean
code. But if theSalesRepBean
class gets the identifiers from theCustomerBean
entity bean, no coding changes would be required. The two approaches present a trade-off: performance versus flexibility. TheSalesRepBean
example opts for flexibility, loading thecustomerIds
variable by calling thefindBySalesRep
andgetPrimaryKey
methods ofCustomerBean
. Here is the code for theloadCustomerIds
method:private 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()); } }If a customer's sales representative changes, the client program updates the database by calling the
setSalesRepId
method of theCustomerBean
class. The next time a business method of theSalesRepBean
class is called, theejbLoad
method invokesloadCustomerIds
, which refreshes thecustomerIds
variable. (To ensure thatejbLoad
is invoked before each business method, set the transaction attributes of the business methods toRequired
.) For example, theSalesRepClient
program changes thesalesRepId
for a customer named Mary Jackson as follows:The
salesRepId
value 543 identifies a sales representative named Janice Martin. To list all of Janice's customers, theSalesRepClient
program invokes thegetCustomerIds
method, iterates through theArrayList
of identifiers, and locates eachCustomerBean
entity bean by calling itsfindByPrimaryKey
method:SalesRep 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); }Running the SalesRepBean Example
- Create the
salesrep
database table.- In
deploytool
, deploy theSalesRepApp.ear
file, which is in this directory:
<
INSTALL
>/j2eetutorial14/examples/ejb/provided-ears/
- Run the client.
- In a terminal window, go to this directory:
<
INSTALL
>/j2eetutorial14/examples/ejb/salesrep/
- Type the following command on a single line:
appclient -client SalesRepAppClient.jar
- The client should display the following lines:
. . .
customerId = 221
customerId = 388
customerId = 456
customerId = 844
987: Mary Jackson
221: Alice Smith
388: Bill Williamson
456: Joe Smith
844: Buzz Murphy
. . .Many-to-Many Relationships
In a many-to-many relationship, each entity can be related to multiple occurrences of the other entity. For example, a college course has many students and each student may take several courses. In a database, this relationship is represented by a cross-reference table containing the foreign keys. In Figure 26-4, the cross-reference table is the
enrollment
table. These tables are accessed by theStudentBean
,CourseBean
, andEnrollerBean
classes.
Figure 26-4 Many-to-Many Relationship: Students and Courses
The source code for this example is in this directory:
The
StudentBean
andCourseBean
classes are complementary. Each class contains anArrayList
of foreign keys. TheStudentBean
class contains anArrayList
namedcourseIds
, which identifies the courses the student is enrolled in. Similarly, theCourseBean
class contains anArrayList
namedstudentIds
.The
ejbLoad
method of theStudentBean
class adds elements to thecourseIds
ArrayList
by callingloadCourseIds
, a private method. TheloadCourseIds
method gets the course identifiers from theEnrollerBean
session bean. The source code for theloadCourseIds
method follows:private 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()); } }Invoked by the
loadCourseIds
method, thegetCourseIds
method of theEnrollerBean
class queries theenrollment
table:Only the
EnrollerBean
class accesses theenrollment
table. Therefore, theEnrollerBean
class manages the student-course relationship represented in theenrollment
table. If a student enrolls in a course, for example, the client calls theenroll
business method, which inserts a row:If a student drops a course, the
unEnroll
method deletes a row:And if a student leaves the school, the
deleteStudent
method deletes all rows in the table for that student:The
EnrollerBean
class does not delete the matching row from thestudent
table. That action is performed by theejbRemove
method of theStudentBean
class. To ensure that both deletes are executed as a single operation, you must ensure that they belong to the same transaction. See Chapter 30 for more information.Running the EnrollerBean Example
- Create the
enroller
database table.- In
deploytool
, deploy theEnrollerApp.ear
file, which is in this directory:
<
INSTALL
>/j2eetutorial14/examples/ejb/provided-ears/
- Run the client.
- In a terminal window, go to this directory:
<
INSTALL
>/j2eetutorial14/examples/ejb/enroller/
- Type the following command on a single line:
appclient -client EnrollerAppClient.jar
- The client should display the following lines:
. . .
Denise Smith:
220 Power J2EE Programming
333 XML Made Easy
777 An Introduction to Java Programming
An Introduction to Java Programming:
823 Denise Smith
456 Joe Smith
388 Elizabeth Willis
. . .
Download
FAQ History |
API
Search Feedback |
All of the material in The J2EE(TM) 1.4 Tutorial is copyright-protected and may not be published in other works without express written permission from Sun Microsystems.