一起学习
Container-managed relations for the 21st century
EJB 2.0's new relationship facilities assist developers in rapid deployment
Summary
Enterprise JavaBeans (EJB) 2.0's Container-Managed Persistence (CMP) specification allows for fine-grained control over entity bean relationships. The container can also persist these relationships instead of having the bean author control them. In this article, Allen Fogleson explains and demonstrates how to use such relationships in CMP 2 entity beans. (3,000 words; April 19, 2002)
By Allen Fogleson
ava developers often create complex relationships when they program applications to solve real-world business problems. Therefore, it was no surprise that when programmers began developing enterprise applications using Enterprise JavaBeans (EJB), they continued to describe their business models with complex relationships. In the original specification, EJBs, however, made such complex relationships more difficult because developers had little control over when an EJB was activated or passivated. In response, developers often wrote complex logic to ensure object persistence within these relationships.
Luckily, the EJB specification developers, recognizing the problem, added container-managed relationships (CMR) in EJB 2.0.
What are these relationships? How do you describe them? How do you encode them in your Java classes? In this article I answer those questions and others.
Note: I assume you understand Java and EJB. Although I give a CMP 2 (container-manage persistence) overview, you won't find an exhaustive CMP tutorial here. See Resources for more articles on EJB and CMP.
Also note: You can download this article's code examples from Resources.
CMP 2 overview
Before you can understand EJB relationships, you must first understand how EJBs work. How are their persistent fields defined? How do you define the relationships? What are the relationships' return objects?
The EJB 1.0 and 1.1 specifications identified container-managed fields by placing attributes in the bean, then described the fields in the deployment descriptor. Although other entity beans could reside within the parent entity bean, the container did not automatically handle their persistence or loading. The bean developer, therefore, had to write additional code, normally in the ejbCreate(), ejbActivate(), and ejbPassivate() methods to handle these additional entity beans. The developer, as his only alternative, could define entity beans using bean-managed persistence.
The EJB 2.0 specification changed the situation dramatically. Among the specification's many changes, container-managed fields now no longer have attributes to identify them within the bean; instead they have abstract getters and setters to identify them. Using the getters and setters, you can now also include other entity beans. The getters' return types depend upon the relationships type. If a single child-bean instance returns, the instance is the child bean's local interface. In contrast, if multiple child-bean instances return, the instances are a java.util.Collection or a java.util.Set. The bean developer can then describe the relationship, defined within the deployment descriptor's relationship tag, in the EJB's deployment descriptor. Here's an example:
...
User-Demographics
User-Demographics
One
Users
demographics
Demographics-belongs-to-Users
One
Demographics
...
The example above defines a user bean with a one-to-one, unidirectional relationship to a demographics bean. Let's further examine each XML tag in the deployment descriptor.
The relationships tag tells the container you describe the relationships of the beans contained in this deployment. Each relationship starts with the ejb-relation tag. The ejb-relation-name tag is a human-readable description of the relationship. The ejb-relationship-role tags, of which there are two in each ejb-relation, describe each entity bean's role in this relationship. Each ejb-relationship-role tag contains a human-readable ejb-relationship-role-name tag and describes that bean's role in the relationship. The multiplicity tag describes whether the bean is on the relationship's one or many side. The relationship-role source contains an ejb-name tag, which is the entity bean that participates in the relationship. The ejb-name tag must match one of the EJBs defined in the EJB jar file.
The cmr-field tag is optional. If the described bean lacks a CMR field, it will not be included as demonstrated in the second ejb-relationship-role tag above. However, you must have the cmr-field tag for beans that have a CMR field. The text within the tag contains the cmr-field-name tag describing the CMR field to use in the relationship. Finally, the cascade-delete tag, included in the bean that is the relationship's child, tells the container that if the relationship's parent is deleted, the parent's children should also be deleted.
Relationships overview
The new EJB 2.0 persistence scheme can model eight relationships:
One-to-one unidirectional: The parent bean references one child entity-bean instance. A customer with an address serves as a classic example: customers have meaning to us, but addresses without customers mean little.
One-to-one bidirectional: Both the parent and child beans reference each other. A classic example of this relationship: a customer and a credit card. Given a customer, you need to find his credit card to charge him for a purchase. The accounting department might also need to look up a customer based upon his credit card information.
One-to-many unidirectional: In this relationship, the parent entity bean references many child entity beans, and the child entity beans mean little without the parent. Example: a customer and her associated phone numbers. Knowing a customer, you want to find her telephone numbers; however, you would rarely need to find a customer by knowing a telephone number.
One-to-many bidirectional: Each bean in the relationship references every other bean. The parent bean has many child beans, and each child bean references its parent. As an example, think of a customer and his orders. A customer can have many orders, either pending or complete, and a shipping department might wish to find out to which customer a particular order belongs.
Many-to-one unidirectional: Many parent entity beans reference a single child entity bean. However, the child entity bean does not refer back to the parents. Airline reservations serve as a classic example. Although a flight may occur multiple times, reservations pertain to a single flight. From the customer's view, you just want to know the given reservation's flight information.
Many-to-one bidirectional: Although you can easily model many-to-one bidirectional relationships under the persistence scheme, they are not really unique relationships because they are identical to one-to-many bidirectional relationships.
Many-to-many unidirectional: Many parent beans reference many child beans. The child beans either mean nothing without the parent, or you don't need to look up a parent bean based on a child bean. Expanding on the airline reservation example, not all reservations occur with direct flights. A single reservation may include multiple flights; moreover, many reservations may exist for the same flight. Now from our customer's view, you want to know all the flights in the reservation.
Many-to-many bidirectional: Many parent beans reference many child beans, which likewise reference many parent beans. An event schedule illustrating the relationship of events and locations serves as an example: many events occur in many locations. Given an event, you may want to find its locations. Given a location, you may also wish to find all the events occurring there.
From the list above, you see three major relationship types: one-to-one, one-to-many, and many-to-many. We will explore each in more detail. First, however, let's explore how the EJB 2.0 specification handles such relationships.
Local interfaces
As previously mentioned, the entity bean models each relationship type using abstract assessors. The return type is either a local interface or a local interface collection. Although local interfaces are not unique to entity beans, their use in constructing relationships does restrict their usefulness. Since relationships use local interfaces that can work only within the same VM, only relationships within a single VM can occur. Therefore, you cannot create relationships with other distributed entity beans. Although that restriction hinders many possibilities, it offers one large advantage: local interfaces are extremely fast. Because a remote call's overhead no longer exists, local interface calls act just like other local method calls to a regular Java class.
One-to-one relationships
Now that you understand EJB relationship basics, let's model your first relationship. For simplicity's sake, start with an easy one-to-one relationship: the customer and address relationship. Because in its simplest sense, the customer address relationship is a one-to-one unidirectional relationship, you model it with one customer bean and one address bean. I keep the entity beans simple to concentrate on defining the relationship.
Begin by creating the abstract class defining the customer bean. Although the entity beans presented would work, I've simplified them to demonstrate how the relationships work. In a real system, you would not create entity beans with such primary keys:
package relationships;
import javax.ejb.*;
import java.math.*;
import relationship.*;
public abstract class CustomerBean implements EntityBean {
// The primary key
public abstract String getCustomerName();
// The container-managed relationship field.
public abstract Address getAddress();
public abstract void setAddress(Address addr);
.
.
.}
Notice that I've declared the fields holding the relationship values the same as the entity bean's regular container-managed field. You simply declare an abstract field with the return type being the relationship bean's local interface. The Address bean implementation class proves easier than the CustomerBean class, as it contains no relationship-holding fields:
package relationships;
import javax.ejb.*;
import java.math.*;
import relationship.*;
public abstract class AddressBean implements EntityBean {
// Our primary key
public abstract String getAddress();
.
.
.
}
You don't do much in the entity bean to declare relationships. A relationship's true power, however, lies in that it can be unidirectional, bidirectional, one-to-one, one-to-many, or many-to-many. Although you can determine from this simple example everything needed for a one-to-one relationship, you must still declare the relationship in the deployment descriptor. The deployment descriptor excerpt looks like:
.
.
.
Customer contains an address
Customer-Address
One
Customer
address
Address-belongs-to-Customer
One
Demographics
.
.
.
As you can see, modeling one-to-one unidirectional relationships is easy. You can now get a customer's information and address from the customer bean. Now suppose your business user wants to determine demographic information by finding customers based on location. To do so, you must code logic into the Address entity bean. With CMR, you can quickly code the logic by minimally changing the entity beans. In the Address bean, add one container-managed field:
.
.
.
// A container-managed field holding the customer
public abstract Customer getCustomer();
public abstract void setCustomer(Customer customer);
.
.
.
Note that I added an unnecessary setter to the class. Its use depends on the application's business requirements. To wit, if you never create customers from addresses, leave the setter out and use the getter to find a customer from an address.
You also must slightly change the deployment descriptor to change the relationship to bidirectional:
.
.
.
One
Address
customer
.
.
.
By changing two code lines -- discounting any additional finder or business methods -- and three-lines in the deployment descriptor, you can now find customers from an address or find addresses belonging to a customer. In fact, if you only used one-to-one relationships, you'd nevertheless solve many of your development problems.
One-to-many and many-to-one relationships
Although one-to-one relationships prove useful, they do not completely meet your needs. You'll find one-to-many relationships more useful than one-to-one relationships. Indeed, you could extend the simple customer and address example to a one-to-many relationship if you wish to track multiple addresses, such as shipping, billing, home, and work addresses.
As another one-to-many relationship, think of a customer and her telephone information, for which you make a simple telephone bean:
package relationships;
import javax.ejb.*;
import java.math.*;
import relationship.*;
public abstract class CustomerBean implements EntityBean {
// The telephone number
public abstract String getPhoneNumber();
// The telephone type
public abstract String getPhoneType();
public abstract void setPhoneType(String);
.
.
.
The bean lets you set the telephone number and type. With the phone bean developed, you can now build the relationship in the CustomerBean. Because either the Collection or Set class returns many entity bean instances, you must choose one as the abstract field type in CustomerBean.
How to choose? The Collection class contains all the entity beans in the relationship, regardless of distinctness. In contrast, the Set class contains only unique, related entity beans. Because the beans have the telephone number as their primary key, you have encoded uniqueness into the bean, so use the Collection object as the CustomerBean return type. Modify the bean so it contains the CMR phone field:
package relationships;
import java.util.Collection;
import javax.ejb.*;
import java.math.*;
import relationship.*;
public abstract class CustomerBean implements EntityBean {
// The primary key
public abstract String getCustomerName();
// The container-managed relationship field.
public abstract Address getAddress();
public abstract void setAddress(Address addr);
// The container-managed relationship field for phones
public abstract Collection getPhone();
public abstract void setPhone(Collection phones);
.
.
.
}
With the entity beans complete, you can now describe the relationship in the deployment descriptor so the container can properly manage the relationship. To do so, add another ejb-relation tag to the deployment descriptor:
.
.
.
Customer contains many phones
Customer-Phone
One
Customer
phone
Phones-belongs-to-Customer
Many
Phone
.
.
.
That's it to describe a unidirectional, one-to-many relationship.
Much like the one-to-one relationship, you can easily change a one-to-many relationship to a bidirectional relationship by adding a CMR field in the PhoneBean class. You also must slightly adjust the deployment descriptor to make the relationship bidirectional. First, modify the PhoneBean class:
.
.
.
// A container managed field holding the customer
public abstract Customer getCustomer();
public abstract void setCustomer(Customer customer);
.
.
.
You should recognize the code above -- it's identical to how you modified the AddressBean for bidirectional behavior. The deployment descriptor is the same as the address example, except you modify the phone bean's relationship descriptor. You can now find a customer from any of his addresses, or you can find all addresses by knowing the customer.
Many-to-many relationships
Although you won't see the final relationship type -- many-to-many -- as often as the other two types, sometimes it's critical to an application. To illustrate many-to-many relationships, I'll employ an uncommon example. Let's assume you have developed a Website with its pages stored in a database. Whenever a user visits a page, you want to know whether he's a return visitor. To accomplish that goal, you expand the aforementioned CustomerBean, as it can also describe a site visitor:
package relationships;
import java.util.Collection;
import javax.ejb.*;
import java.math.*;
import relationship.*;
public abstract class CustomerBean implements EntityBean {
// The primary key
public abstract String getCustomerName();
// The container managed relationship field.
public abstract Address getAddress();
public abstract void setAddress(Address addr);
// The container managed relationship field for phones
public abstract Collection getPhone();
public abstract void setPhone(Collection phones);
// A container managed field for web page visits
public abstract Collection getWebPages();
public abstract void setWebPages(Collection pages);
.
.
.
}
Again, you employ a collection because you will return many Webpage instances for each customer. Next, define a WebpageBean class to use in the deployment descriptor:
package relationships;
import java.util.Collection;
import javax.ejb.*;
import java.math.*;
import relationship.*;
public abstract class CustomerBean implements EntityBean {
// The primary key
public abstract String getPageName();
.
.
.
}
Although it having beans with one field is highly contrived, that's all that's necessary to demonstrate the relationship, so let's add the appropriate relationship to the deployment descriptor:
.
.
.
Many Customers contain many webpages
Customer-Pages
Many
Customer
webPages
Address-belongs-to-Customer
Many
Webpage
.
.
.
Note the difference in the deployment descriptor: you no longer add the cascade-delete tag. Why? If you do a cascade delete and delete one customer, you will delete all the database's Webpages, leaving you with many customers who apparently never visited a single page.
You now have a CustomerBean EJB that can find all the Webpages a customer visited. However, as soon as you finish, your boss asks for some statistical reporting functionality. He wants to know, for any Webpage, all the customers who visited that page. Armed with your newfound EJB relationship skills, you add a CMR field to the WebpageBean:
.
.
.
// The Customers who have visited us
public abstract Collection getCustomers();
public abstract void setCustomers(Collection customers);
.
.
.
}
You also modify your deployment descriptor so Webpages also include a relationship field:
.
.
.
Many Customers contain many webpages
Customer-Pages
Many
Customer
webPages
Address-belongs-to-Customer
Many
Webpage
customers
.
.
.
After you recompile, your entity bean can get all the customers for any Webpage or it can get all the Webpages for any customer.
Relationship review
Even this article's simple examples demonstrate that container-managed relationships prove a powerful feature for developers. Application developers can now design and encode complex relationships among many entity beans without resorting to custom logic in their entity-bean implementation class. You can have one-to-one, one-to-many, or many-to-many relationships without worrying about the entity bean's state. The container now ensures child beans properly persist, activate, and load. The enterprise specification's initial aim was to eleminate the application developer's need to control transactional and persistence logic. EJB 2.0's new relationship capabilities take the extra step necessary to really remove the developer away from the persistence details in entity beans.
About the author
Allen Fogleson, a senior developer at Cross Media Marketing, has worked in the IT industry for 16 years, serving in numerous roles including developer, senior developer, senior technical project manager, and information systems officer. For the past 5 years, Allen has designed and developed enterprise applications using Java technologies, concentrating exclusively on EJB projects over the past 3 years.
下载本文示例代码
Container-managed relations for the 21st centuryContainer-managed relations for the 21st centuryContainer-managed relations for the 21st centuryContainer-managed relations for the 21st centuryContainer-managed relations for the 21st centuryContainer-managed relations for the 21st centuryContainer-managed relations for the 21st centuryContainer-managed relations for the 21st centuryContainer-managed relations for the 21st centuryContainer-managed relations for the 21st centuryContainer-managed relations for the 21st centuryContainer-managed relations for the 21st century