|
Home TOC Index |
|
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
storagebintable might have a one-to-one relationship with awidgettable. 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 5-1 illustrates the
storagebinandwidgettables. Because thestoragebiniduniquely identifies a row in thestoragebintable, it is that table's primary key. Thewidgetidis the primary key of thewidgettable. The two tables are related because thewidgetidis also a column in thestoragebintable. By referring to the primary key of thewidgettable, thewidgetidin thestoragebintable identifies which widget resides in a particular storage bin in the warehouse. Because thewidgetidof thestoragebintable 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 5-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 thestoragebintable has a row with awidgetidof 344, then the widget table should also have a row whosewidgetidis 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
storagebintable 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 the
j2eetutorial/examples/src/ejb/storagebindirectory. To compile the code, go to thej2eetutorial/examplesdirectory and typeantstoragebin. A sampleStorageBinApp.earfile is in the j2eetutorial/examples/earsdirectory.The
StorageBinBeanandWidgetBeanclasses illustrate the one-to-one relationship of thestoragebinandwidgettables. TheStorageBeanclass contains variables for each column in thestoragebintable, including the foreign key,widgetId:private String storageBinId; private String widgetId; private int quantity;The
ejbFindByWidgetIdmethod of theStorageBeanclass returns thestorageBinIdthat 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
ejbFindByWidgetIdmethod locates thewidgetIdby querying the database in theselectByWidgetIdmethod:private 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; }To find out in which storage bin a widget resides, the
StorageBinClientprogram calls thefindByWidgetIdmethod:String widgetId = "777"; StorageBin storageBin = storageBinHome.findByWidgetId(widgetId); String storageBinId = (String)storageBin.getPrimaryKey(); int quantity = storageBin.getQuantity();Running the StorageBinEJB Example
- Create the
storagebindatabase table.- Deploy the
StorageBinApp.earfile (located in thej2eetutorial/examples/earsdirectory).- Run the client.
- Go to the
j2eetutorial/examples/earsdirectory.- Set the
APPCPATHenvironment variable toStorageBinAppClient.jar.- Type the following command on a single line:
runclient -client StorageBinApp.ear -name StorageBinClient -textauth- At the login prompts, enter
guestfor the user name andguest123for the password.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
teamtable and aplayertable. 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 theteamtable'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.
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 the table should be represented with a helper class. 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 5-2.
Figure 5-2 One-to-Many Relationship: Order and Line Items
Not only does a line item belong to an order, it also does not exist without the order. Therefore, the
lineitemstable 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 source code for the following example is in the
j2eetutorial/examples/src/ejb/orderdirectory. To compile the code, go to thej2eetutorial/examplesdirectory and typeantorder. A sampleOrderApp.earfile is in the j2eetutorial/examples/earsdirectory.The
LineItemandOrderBeanclasses show how to implement a one-to-many relationship with a helper class (LineItem). The instance variables in theLineItemclass correspond to the columns in thelineitemstable. TheitemNovariable matches the primary key for thelineitemstable, and theorderIdvariable represents the table's foreign key. Here is the source code for theLineItemclass: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
OrderBeanclass contains anArrayListvariable namedlineItems. Each element in thelineItemsvariable is aLineItemobject. ThelineItemsvariable is passed to theOrderBeanclass in theejbCreatemethod. For everyLineItemobject in thelineItemsvariable, theejbCreatemethod inserts a row into thelineitemstable. It also inserts a single row into theorderstable. The code for theejbCreatemethod 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
OrderClientprogram creates and loads anArrayListofLineItemobjects. The program passes thisArrayListto the entity bean when it invokes thecreatemethod: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
OrderBeanclass also access both database tables. TheejbRemovemethod, for example, deletes not only a row from theorderstable, but also deletes all corresponding rows in thelineitemstable. TheejbLoadandejbStoremethods synchronize the state of anOrderEJBinstance, including thelineItemsArrayList, with theordersandlineitemstables.The
ejbFindByProductIdmethod enables clients to locate all orders that have a particular line item. This method queries thelineitemstable for all rows with a particularproductId. The method returns aCollectionofproductIdStringobjects. TheOrderClientprogram iterates through theCollectionand 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 OrderEJB Example
- Create the
ordersdatabase table:.- Deploy the
OrderApp.earfile (located in thej2eetutorial/examples/earsdirectory).- Run the client.
An Entity Bean for the Child Table
You should consider building an entity bean for a child table under the following conditions:
- The information in the child table is not dependent on the parent table.
- The business entity of the child table could exist without that of the parent table.
- The child table might be accessed by another application that does not access the parent table.
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 with a database application. In the database, each row in the
salesreptable (parent) matches multiple rows in thecustomertable (child). Figure 5-3 illustrates this relationship.Figure 5-3 One-to-Many Relationship: Sales Representative and Customers
The
SalesRepBeanandCustomerBeanentity bean classes implement the one-to-many relationship of thesalesandcustomertables.The source code for this example is in the
j2eetutorial/examples/src/ejb/salesrepdirectory. To compile the code, go to thej2eetutorial/examplesdirectory and typeantsalesrep. A sampleSalesRepApp.earfile is in the j2eetutorial/examples/earsdirectory.The
SalesRepBeanclass contains a variable namedcustomerIds, which is anArrayListofStringelements. TheseStringelements identify which customers belong to the sales representative. Because thecustomerIdsvariable reflects this relationship, theSalesRepBeanclass must keep the variable up to date.The
SalesRepBeanclass instantiates thecustomerIdsvariable in thesetEntityContextmethod, not inejbCreate. The container invokessetEntityContextjust once--when it creates the bean instance--ensuring thatcustomerIdsis instantiated just once. Because the same bean instance can assume different identities during its life cycle, instantiatingcustomerIdsinejbCreatemight cause multiple and unnecessary instantiations. Therefore, theSalesRepBeanclass instantiates thecustomerIdsvariable insetEntityContext:public 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()); } }Invoked by the
ejbLoadmethod,loadCustomerIdsis a private method that refreshes thecustomerIdsvariable. There are two approaches when coding a method such asloadCustomerIds: fetch the identifiers from thecustomerdatabase table or get them from theCustomerEJBentity bean. Fetching the identifiers from the database might be faster, but exposes the code in theSalesRepBeanclass to theCustomerEJBbean's underlying database table. In the future, if you were to change theCustomerEJBbean's table (or move the bean to a different J2EE server), you might need to change theSalesRepBeancode. But if theSalesRepBeanclass gets the identifiers from theCustomerEJBentity bean, no coding changes would be required. The two approaches present a trade-off: performance versus flexibility. TheSalesRepEJBexample opts for flexibility, loading thecustomerIdsvariable by calling thefindSalesRepandgetPrimaryKeymethods ofCustomerEJB. Here is the code for theloadCustomerIdsmethod: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
setSalesRepIdmethod of theCustomerBeanclass. The next time a business method of theSalesRepBeanclass is called, theejbLoadmethod invokesloadCustomerIds, which refreshes thecustomerIdsvariable. (To ensure thatejbLoadis invoked before each business method, set the transaction attributes of the business methods to Required.) For example, theSalesRepClientprogram changes thesalesRepIdfor a customer named Mary Jackson as follows:Customer mary = customerHome.findByPrimaryKey("987"); mary.setSalesRepId("543");The
salesRepIdvalue 543 identifies a sales representative named Janice Martin. To list all of Janice's customers, theSalesRepClientprogram invokes thegetCustomerIdsmethod, iterates through theArrayListof identifiers, and locates eachCustomerEJBentity bean by calling itsfindByPrimaryKeymethod: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 SalesRepEJB Example
- Create the database tables.
- Deploy the
SalesRepApp.earfile (located in thej2eetutorial/examples/earsdirectory).- Run the client.
- Go to the
j2eetutorial/examples/earsdirectory.- Set the
APPCPATHenvironment variable toSalesRepAppClient.jar.- Type the following command on a single line:
runclient -client SalesRepApp.ear -name SalesRepClient -textauth- At the login prompts, enter
guestfor the user name andguest123for the password.Many-to-Many Relationships
In a many-to-many relationship, each entity may 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 5-4, the cross reference table is the
enrollmenttable. These tables are accessed by theStudentBean,CourseBean, andEnrollerBeanclasses.Figure 5-4 Many-to-Many Relationship: Students and Courses
The source code for this example is in the
j2eetutorial/examples/src/ejb/enrollerdirectory. To compile the code, go to thej2eetutorial/examplesdirectory and typeant enroller. A sampleEnrollerApp.earfile is in the j2eetutorial/examples/earsdirectory.The
StudentBeanandCourseBeanclasses are complementary. Each class contains anArrayListof foreign keys. TheStudentBeanclass, for example, contains anArrayListnamedcourseIds, which identifies the courses the student is enrolled in. Likewise, theCourseBeanclass contains anArrayListnamedstudentIds.The
ejbLoadmethod of theStudentBeanclass adds elements to thecourseIdsArrayListby callingloadCourseIds, a private method. TheloadCourseIdsmethod gets the course identifiers from theEnrollerEJBsession bean. The source code for theloadCourseIdsmethod 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
loadCourseIdsmethod, thegetCoursesmethod of theEnrollerBeanclass queries theenrollmenttable:select courseid from enrollment where studentid = ?Only the
EnrollerBeanclass accesses theenrollmenttable. Therefore, theEnrollerBeanclass manages the student-course relationship represented in theenrollmenttable. If a student enrolls in a course, for example, the client calls theenrollbusiness method, which inserts a row:insert into enrollment values (studentid, courseid)If a student drops a course, the
unEnrollmethod deletes a row:delete from enrollment where studentid = ? and courseid = ?And if a student leaves the school, the
deleteStudentmethod deletes all rows in the table for that student:delete from enrollment where student = ?The
EnrollerBeanclass does not delete the matching row from thestudenttable. That action is performed by theejbRemovemethod of theStudentBeanclass. To ensure that both deletes are executed as a single operation, they should belong to the same transaction. See Chapter 14 for more information.Running the EnrollerEJB Example
- Create the database tables.
- Deploy the
EnrollerApp.earfile (located in thej2eetutorial/examples/earsdirectory).- Run the client.
- Go to the
j2eetutorial/examples/earsdirectory.- Set the
APPCPATHenvironment variable toEnrollerAppClient.jar.- Type the following command on a single line:
runclient -client EnrollerApp.ear -name EnrollerClient -textauth- At the login prompts, enter
guestfor the user name andguest123for the password.
|
Home TOC Index |
|
Search
Feedback |