In Chapter 6, you learned about basic EJB 2.0 container-managed persistence. This material included coverage of container-managed persistence fields and an introduction to a basic container-managed relationship field. In this chapter, we will continue to develop the Customer EJB and discuss in detail each of the seven possible relationships that entity beans can have with each other.
For entity beans to model real-world business concepts, they must be capable of forming complex relationships with each other. This was difficult to accomplish in EJB 1.1 container-managed persistence because of the simplicity of the programming model. In EJB 1.1, entity beans could have persistence fields but not relationship fields.
In EJB 2.0, relationship fields can model complex relationships between entity beans. In Chapter 6, we demonstrated a one-to-one relationship between the Customer and Address EJBs. This relationship was unidirectional; the Customer had a reference to the Address, but the Address did not have a reference back to the Customer. This is a perfectly legitimate relationship, but other relationships are also possible. For example, each Address could also reference its Customer. This is an example of a bidirectional one-to-one relationship, in which both participants maintain references to one another. In addition to one-to-one relationships, entity beans can have one-to-many, many-to-one, and many-to-many relationships. For example, the Customer EJB can have many phone numbers, but each phone number belongs to only one Customer (a one-to-many relationship). A Customer may also have been on many Cruises in the past, and each Cruise will have had many Customers (a many-to-many relationship).
Seven types of relationships can exist between EJBs. This chapter examines those relationships and how the beans’ code and deployment descriptors work together to define the relationships. First, let’s look at the different types of relationships that are possible. There are four types of cardinality: one-to-one, one-to-many, many-to-one, and many-to-many. On top of that, each relationship can be either unidirectional or bidirectional. That yields eight possibilities, but if you think about it, you’ll realize that one-to-many and many-to-one bidirectional relationships are actually the same thing. Thus, there are only seven distinct relationship types. To understand the relationships, it helps to think about some simple examples. We’ll expand on the following examples in the course of the chapter:
The relationship between a customer and an address. You clearly want to be able to look up a customer’s address, but you probably don’t care about looking up an address’s customer.
The relationship between a customer and a credit card number. Given a customer, you obviously want to be able to look up his credit card number. Given a credit card number, it is also conceivable that you would want to look up the customer who owns the credit card.
The relationship between a customer and a phone number. A customer can have many phone numbers (business, home, cell, etc.). You might need to look up a customer’s phone number, but you probably wouldn’t use one of those numbers to look up the customer.
The relationship between a cruise and a reservation. Given a reservation, you want to be able to look up the cruise for which the reservation was made. And given a cruise, you want to be able to look up all the reservations for that cruise. Note that a many-to-one bidirectional relationship is just another perspective on the same concept.
The relationship between a cruise and a ship. You want to be able to look up the ship that will be used for a particular cruise, and many cruises share the same ship, though at different times. It’s less useful to look up the ship to see which cruises are associated it, though if you want this capability, you can implement a many-to-one bidirectional relationship.
The relationship between a reservation and a cabin. It’s possible to make a reservation for multiple cabins, and you clearly want to be able to look up the cabin assigned to a reservation. However, you’re not likely to want to look up the reservation associated with a particular cabin. (If you think you need to do so, you’d implement it as a bidirectional relationship.)
The relationship between a cruise and a customer. A customer can make reservations on many cruises, and each cruise has many customers. You clearly want to be able to look up both the cruises on which a customer has a booking, and the customers that will be going on any given cruise.
In Chapter 6, you
learned how to form a basic relationship between the Customer and
Address entity beans using the abstract programming model. In
reality, the abstract programming model is only half the equation. In
addition to declaring abstract accessor methods, a bean developer
must describe the cardinality and direction of the entity-to-entity
relationships in the bean’s deployment descriptor. This is
handled in the
<relationships>
section of the XML deployment descriptor.
As we discuss each type of relationship in the following sections, we
will examine both the abstract programming model and the XML
elements. The purpose of this section is to introduce you to the
basic elements used in the XML deployment descriptor, to better
prepare you for subsequent sections on specific relationship types.
In this book we always refer to the Java programming idioms used to describe relationships—specifically, the abstract accessor methods—as the abstract programming model. When referring to the XML deployment descriptor elements, we use the term abstract persistence schema . In the EJB 2.0 specification the term abstract persistence schema actually refers to both the Java idioms and the XML elements, but this book separate these concepts so we can discuss them more easily.
An
entity bean’s abstract persistence schema is defined in the
<relationships>
section of the XML
deployment descriptor for that bean. The
<relationships>
section falls between the
<enterprise-beans>
section and the
<assembly-descriptor>
section. Within the
<relationships>
element, each entity-to-entity relationship is defined in a separate
<ejb-relation>
element:
<ejb-jar> <enterprise-beans> ... </enterprise-beans> <relationships> <ejb-relation> ... </ejb-relation> <ejb-relation> ... </ejb-relation> </relationships> <assembly-descriptor> ... </assembly-descriptor> </ejb-jar>
Defining relationship fields requires that an
<ejb-relation>
element be added to the XML
deployment descriptor for each entity-to-entity relationship. These
<ejb-relation>
elements complement the
abstract programming model. For each pair of abstract accessor
methods that define a relationship field, there is an
<ejb-relation>
element in the deployment
descriptor. EJB 2.0 requires that the entity beans that participate
in a relationship be defined in the same XML deployment descriptor.
Here is a partial listing of the deployment descriptor for the Customer and Address EJBs, with the emphasis on the elements that define the relationship:
<ejb-jar> ... <enterprise-beans> <entity> <ejb-name>CustomerEJB
</ejb-name> <local-home>com.titan.customer.CusomterHomeLocal</local-home> <local>com.titan.customer.CustomerLocal</local> ... </entity> <entity> <ejb-name>AddressEJB
</ejb-name> <local-home>com.titan.address.AddressHomeLocal</local-home> <local>com.titan.address.AddressLocal</local> ... </entity> ... </enterprise-beans> <relationships><ejb-relation>
<ejb-relation-name>Customer-Address</ejb-relation-name> <ejb-relationship-role> <ejb-relationship-role-name> Customer-has-an-Address </ejb-relationship-role-name> <multiplicity>One</multiplicity> <relationship-role-source> <ejb-name>CustomerEJB
</ejb-name> </relationship-role-source> <cmr-field> <cmr-field-name>homeAddress</cmr-field-name> </cmr-field> </ejb-relationship-role> <ejb-relationship-role> <ejb-relationship-role-name> Address-belongs-to-Customer </ejb-relationship-role-name> <multiplicity>One</multiplicity> <relationship-role-source> <ejb-name>AddressEJB
</ejb-name> </relationship-role-source> </ejb-relationship-role></ejb-relation>
</relationships> </ejb-jar>
All relationships between the Customer EJB and other entity beans,
such as CreditCard, Address, and Phone, require that we define an
<ejb-relation>
element to complement the
abstract accessor methods.
Every relationship may have a relationship name, which is declared in the
<ejb-relation-name>
element. This serves to identify the relationship for individuals
reading the deployment descriptor or for deployment tools, but
it’s not required.
Every <ejb-relation>
element has exactly two
<ejb-relationship-role>
elements, one for each participant in the relationship. In the
previous example, the first
<ejb-relationship-role>
declares the
Customer EJB’s role in the relationship. We know this because
the <relationship-role-source>
element
specifies the <ejb-name>
as
CustomerEJB
. CustomerEJB
is the
<ejb-name>
used in the Customer EJB’s
original declaration in the
<enterprise-beans>
section. The
<relationship-role-source>
element’s
<ejb-name>
must always match an
<ejb-name>
element in the
<enterprise-beans>
section.
The <ejb-relationship-role>
element also
declares the
cardinality,
or
multiplicity
,
of the role. The
<multiplicity>
element can either be One
or
Many
. In this case, the Customer EJB’s
<multiplicity>
element has a value of
One
, which means that every Address EJB has a
relationship with exactly one Customer EJB. The
Address EJB’s <multiplicity>
element
also specifies One
, which means that every
Customer EJB has a relationship with exactly one Address EJB. If the
Customer EJB had a relationship with many Address EJBs, the Address
EBJ’s <multiplicity>
element would be
set to Many
.
In Chapter 6, we defined the Customer EJB as
having abstract accessor methods for getting and setting the Address
EJB in the homeAddress
field, but the Address EJB
did not have abstract accessor methods for the Customer EJB. In this
case, the Customer EJB maintains a reference to the Address EJB, but
the Address EJB doesn’t maintain a reference back to the
Customer EJB. This is a unidirectional relationship, which means that
only one of the entity beans in the relationship maintains a
container-managed relationship field.
If the bean described by the
<ejb-relationship-role>
element maintains a
reference to the other bean in the relationship, that reference must
be declared as a container-managed relationship field
in the
<cmr-field>
element. The <cmr-field>
element is declared
under the <ejb-relationship-role>
element:
<ejb-relationship-role> <ejb-relationship-role-name> Customer-has-an-Address </ejb-relationship-role-name> <multiplicity>One</multiplicity> <relationship-role-source> <ejb-name>CustomerEJB</ejb-name> </relationship-role-source><cmr-field>
<cmr-field-name>homeAddress</cmr-field-name>
</cmr-field>
</ejb-relationship-role>
EJB 2.0 requires that the
<cmr-field-name>
begin with a lowercase letter. For every relationship field defined
by a <cmr-field>
element, there must be a
pair of matching abstract accessor methods in the bean
class. One method in this pair must be defined with the method name
set<cmr-field-name>()
, with the first letter of the
<cmr-field-name>
value changed to uppercase.
The other method is defined as
get<cmr-field-name>()
, also with the first letter of the
<cmr-field-name>
value in uppercase. In the
previous example, the <cmr-field-name>
is
homeAddress
, which corresponds to the
getHomeAddress()
and
setHomeAddress()
abstract accessor methods defined
in the CustomerBean
class:
// bean class code public abstract voidsetHomeAddress
(AddressLocal address); public abstract AddressLocalgetHomeAddress
(); // XML deployment descriptor declaration <cmr-field> <cmr-field-name>homeAddress
</cmr-field-name> </cmr-field>
The return type of the get<cmr-field-name>()
method and the parameter type of the
set<cmr-field-name>()
must be the same. The
type must be the local interface of the entity bean that is
referenced or one of two java.util.Collection
types. In the case of the homeAddress
relationship
field, we are using the Address EJB’s local interface,
AddressLocal
. Collection types are discussed in
more detail in one-to-many, many-to-one, and many-to-many
relationships later in this chapter.
Now that we have established a basic understanding of how elements are declared in the abstract persistence schema, we are ready to discuss each of the seven types of relationships in more detail. In the process, we will introduce additional entity beans that have relationships with the Customer EJB, including the CreditCard, Phone, Ship, and Reservation EJBs.
It’s important to understand that although entity beans may
have both local and remote interfaces, a container-managed
relationship field can use only the entity bean’s local
interface when persisting a relationship. So, for example, it is
illegal to define an abstract accessor method that has an argument
type of javax.ejb.EJBObject
(a remote interface
type). All container-managed relationships are based on
javax.ejb.EJBLocalObject
(local interface)
types.
This chapter discusses several different database table schemas. These schemas are
intended only to demonstrate possible relationships between entities
in the database; they are not prescriptive. For example, the
Address-Customer relationship is manifested by having the
CUSTOMER
table maintain a foreign key to the
ADDRESS
table. This is not how most databases will
be organized—instead, they will probably use a link table or
have the ADDRESS
table maintain a foreign key to
the CUSTOMER
. However, this schema shows how EJB
2.0’s container-managed persistence can support different
database organizations.
Throughout this chapter, we assume that the database tables are created before the EJB application—in other words, that the EJB application is mapped to a legacy database. Some vendors offer tools that generate tables automatically according to the relationships defined between the entity beans. These tools may create schemas that are very different from the ones explored here. In other cases, vendors that support established database schemas may not have the flexibility to support the schemas illustrated in this chapter. As an EJB developer, you must be flexible enough to adapt to the facilities provided by your EJB vendor.
An example of a one-to-one unidirectional relationship is the relationship between the Customer EJB and the Address EJB defined in Chapter 6. In this case, each Customer has exactly one Address and each Address has exactly one Customer. Which bean references which determines the direction of navigation. While the Customer has a reference to the Address, the Address doesn’t reference the Customer. This is a unidirectional relationship because you can go only from the Customer to the Address, and not the other way around. In other words, an Address EJB has no idea who owns it. Figure 7-1 shows this relationship.
As shown in Figure 7-2, one-to-one unidirectional
relationships normally use a fairly typical relational database
schema in which one table contains a foreign key
(pointer) to
another table. In this case, the CUSTOMER
table
contains a foreign key to the ADDRESS
table, but
the ADDRESS
table doesn’t contain a foreign
key to the CUSTOMER
table. This allows records in
the ADDRESS
table to be shared by other tables, a
scenario explored in Section 7.1.10.
The fact that the database schema is not the same as the abstract
persistence schema illustrates that they are somewhat independent.
As you learned in Chapter 6,
abstract accessor methods are used
to define relationship fields in the bean class. When an entity bean
maintains a reference to another bean, it defines a pair of abstract
accessor methods to model that reference. In unidirectional
relationships, which can be navigated only one way, only one of the
enterprise beans defines these abstract accessor methods. Thus,
inside the CustomerBean
class you can call the
getHomeAddress()
/setHomeAddress()
methods to access the Address EJBs, but there are no methods inside
the AddressBean
class to access the Customer EJB.
The Address EJB can be shared between relationship fields of the same
enterprise bean, but it cannot be shared between Customer EJBs. If,
for example, the Customer EJB defines two relationship fields,
billingAddress
and homeAddress
,
as one-to-one unidirectional relationships with the Address EJB,
these two fields can reference the same Address EJB:
public class CustomerBean implements javax.ejb.EntityBean { ... public void setAddress(String street,String city,String state,String zip) { ... address = addressHome.createAddress(street, city, state, zip);this.setHomeAddress(address);
this.setBillingAddress(address);
AddressLocal billAddr = this.getBillingAddress();
AddressLocal homeAddr = this.getHomeAddress();
if(billAddr.isIdentical(homeAddr))
// always true
... } ... }
If at any time you want to make the
billingAddress
different from the
homeAddress
, you can simply set it equal to a
different Address EJB. Sharing a reference to another bean between
two relationship fields in the same entity is sometimes very
convenient, though. In order to support this type of relationship, a
new billing address field might be added to the
CUSTOMER
table:
CREATE TABLE CUSTOMER
(
ID INT PRIMARY KEY,
LAST_NAME CHAR(20),
FIRST_NAME CHAR(20),
ADDRESS_ID INT,
BILLING_ADDRESS_ID INT
}
As the earlier example shows, it is possible for two fields in a bean
(in this case, the homeAddress
and
billingAddress
fields in the Customer EJB) to
reference the same relationship (i.e., a single Address EJB) if the
relationship type is the same. However, it is not possible to share a
single Address EJB between two different Customer EJBs. If, for
example, the home Address of Customer A were assigned as the home
Address of Customer B, the Address would be moved, not shared, so
that Customer A wouldn’t have a home Address any longer. As you
can see in Figure 7-3, Address 2 is initially
assigned to Customer B, but becomes disconnected when Address 1 is
reassigned to Customer B.
This seemingly strange side effect is simply a natural result of how the relationship is defined. The Customer-to-Address EJB relationship was defined as one-to-one, so the Address EJB can be referenced by only one Customer EJB.
If the Customer EJB does not have an Address EJB associated with its
homeAddress
field, the
getHomeAddress()
method will return null. This is
true of all container-managed relationship fields that reference a
single entity bean.
We defined the XML elements for the Customer-Address relationship
earlier in this chapter, so we won’t go over them again. The
<ejb-relation>
element used in that section
declared a one-to-one unidirectional relationship. If, however, the
Customer EJB did maintain two relationship fields with the Address
EJB—homeAddress
and
billingAddress
—each of these relationships
would have to be described in its own
<ejb-relation>
element:
<relationships> <ejb-relation> <ejb-relation-name>Customer-HomeAddress
</ejb-relation-name> <ejb-relationship-role> ... <cmr-field> <cmr-field-name>homeAddress
</cmr-field-name> </cmr-field> </ejb-relationship-role> <ejb-relationship-role> ... </ejb-relationship-role> </ejb-relation> <ejb-relation> <ejb-relation-name>Customer-BillingAddress
</ejb-relation-name> <ejb-relationship-role> ... <cmr-field> <cmr-field-name>billingAddress
</cmr-field-name> </cmr-field> </ejb-relationship-role> <ejb-relationship-role> ... </ejb-relationship-role> </ejb-relation> </relationships>
We can expand our Customer EJB to include a reference to a CreditCard EJB, which maintains credit card information. The Customer EJB will maintain a reference to its CreditCard EJB and the CreditCard EJB will maintain a reference back to the Customer—this makes good sense, since a CreditCard should be aware of who owns it. Since each CreditCard has a reference back to one Customer and each Customer references one CreditCard, we have a one-to-one bidirectional relationship.
The CreditCard EJB has a corresponding CREDIT_CARD
table, so we need to add a CREDIT_CARD
foreign key
to the CUSTOMER
table:
CREATE TABLE CREDIT_CARD ( ID INT PRIMARY KEY NOT NULL, EXP_DATE DATE, NUMBER CHAR(20), NAME CHAR(40), ORGANIZATION CHAR(20),CUSTOMER_ID INT
} CREATE TABLE CUSTOMER ( ID INT PRIMARY KEY, LAST_NAME CHAR(20), FIRST_NAME CHAR(20), HOME_ADDRESS_ID INT, ADDRESS_ID INT,CREDIT_CARD_ID INT
)
One-to-one bidirectional relationships may model relational database
schemas in which the two tables hold foreign keys for one another
(specifically, two rows in different tables point to each other).
Figure 7-4 illustrates how this schema would be
implemented for rows in the CUSTOMER
and
CREDIT_CARD
tables.
It is also possible for a one-to-one bidirectional relationship to be established through a linking table, in which each foreign key column must be unique. This is convenient when you do not want to impose relationships on the original tables. We will use linking tables in one-to-many and many-to-many relationships later in this chapter, but it is important to remember that the database schema used in these examples is purely illustrative. The abstract persistence schema of an entity bean may map to a variety of database schemas; the database schema used in these examples are only one possibility.
To model the relationship between the Customer and CreditCard EJBs,
we’ll need to declare a relationship field named
customer
in the CreditCardBean
class:
public abstract class CreditCardBean extends javax.ejb.EntityBean {
... // relationship fieldspublic abstract CustomerLocal getCustomer();
public abstract void setCustomer(CustomerLocal local);
// persistence fields public abstract Integer getId(); public abstract void setId(Integer id); public abstract Date getExpirationDate(); public abstract void setExpirationDate(Date date); public abstract String getNumber(); public abstract void setNumber(String number); public abstract String getNameOnCard(); public abstract void setNameOnCard(String name); public abstract String getCreditOrganization(); public abstract void setCreditOrganization(String org); // standard callback methods ...}
In this case, we use the Customer EJB’s local interface (assume one has been created) because relationship fields require local interface types. All the relationships explored in the rest of this chapter assume local interfaces. Of course, the limitation of using local interfaces instead of remote interfaces is that you don’t have location transparency. All the entity beans must be located in the same process or Java Virtual Machine ( JVM). Although relationship fields using remote interfaces are not supported in EJB 2.0, it’s likely that support for remote relationship fields will be added in a subsequent version of the specification.
We can also add a set of abstract accessor methods in the
CustomerBean
class for the
creditCard
relationship field:
public class CustomerBean implements javax.ejb.EntityBean { ... public abstract void setCreditCard(CreditCardLocal card); public abstract CreditCardLocal getCreditCard(); ... }
Although a
setCustomer()
method is available in the
CreditCardBean
, we do not have to set the Customer
reference on the CreditCard EJB explicitly. When a CreditCard EJB
reference is passed into the
setCreditCard()
method on the CustomerBean
class, the EJB container will automatically establish the customer
relationship on the CreditCard EJB to point back to the Customer EJB:
public class CustomerBean implements javax.ejb.EntityBean { ... public void setCreditCard(Date exp, String numb, String name, String org) throws CreateException { ... card = creditCardHome.create(exp,numb,name,org); // the CreditCard EJB's customer field will be set automaticallythis.setCreditCard(card);
CustomerLocal customer = card.getCustomer();
if(customer.isIdentical(ejbContext.getEJBLocalObject())
// always true
... } ... }
The rules for sharing a single bean in a one-to-one bidirectional
relationship are the same as those for one-to-one unidirectional
relationships. While the CreditCard EJB may be shared between
relationship fields of the same Customer EJB, it can’t be
shared between different Customer EJBs. As Figure 7-5 shows, assigning Customer A’s
CreditCard
to Customer B disassociates that
CreditCard
from Customer A and moves it to
Customer B.
The
<ejb-relation>
element that defines the Customer-to-CreditCard relationship is
similar to the one used for the Customer-to-Address relationship,
with one important difference—both
<ejb-relationship-role>
elements have a
<cmr-field>
:
<relationships> <ejb-relation> <ejb-relation-name>Customer-CreditCard</ejb-relation-name> <ejb-relationship-role> <ejb-relationship-role-name> Customer-has-a-CreditCard </ejb-relationship-role-name> <multiplicity>One
</multiplicity> <relationship-role-source> <ejb-name>CustomerEJB
</ejb-name> </relationship-role-source><cmr-field>
<cmr-field-name>creditCard</cmr-field-name>
</cmr-field>
</ejb-relationship-role> <ejb-relationship-role> <ejb-relationship-role-name> CreditCard-belongs-to-Customer </ejb-relationship-role-name> <multiplicity>One
</multiplicity> <relationship-role-source> <ejb-name>CreditCardEJB
</ejb-name> </relationship-role-source><cmr-field>
<cmr-field-name>customer</cmr-field-name>
</cmr-field>
</ejb-relationship-role> </ejb-relation> </relationships>
The fact that both participants in the relationship define
<cmr-field>
elements (relationship fields)
tells us that the relationship is bidirectional.
Entity beans can also maintain relationships with multiplicity. This means that one entity bean can aggregate or contain many other entity beans. For example, the Customer EJB may have relationships with many Phone EJBs, each of which represents a phone number. This is very different from the simple one-to-one relationships. One-to-many and many-to-many relationships require the developer to work with a collection of references when accessing the relationship field, instead of a single reference.
To illustrate a one-to-many unidirectional
relationship, we will use a new entity bean, the Phone EJB, for which
we must define a table, the PHONE
table:
CREATE TABLE PHONE ( ID INT PRIMARY KEY NOT NULL, NUMBER CHAR(20), TYPE INT, CUSTOMER_ID INT }
One-to-many unidirectional relationships between the
CUSTOMER
and PHONE
tables could
be manifested in a relational database in a variety of ways. For this
example, we chose to have the PHONE
table include
a foreign key to the CUSTOMER
table.
The table of aggregated data can maintain a column of nonunique
foreign keys to the aggregating table. In the case of the Customer
and Phone EJBs, the PHONE
table maintains a
foreign key to the CUSTOMER
table, and one or more
PHONE
records may contain foreign keys to the same
CUSTOMER
record. In other words, in the database
the PHONE
records point to the
CUSTOMER
records. In the abstract programming
model, however, it is the Customer EJB that points to the Phone
EJBs—two schemas are reversed. How does this work? The
container system hides the reverse pointer so that it appears as if
the Customer is aware of the Phone EJB and not the other way around.
When you ask the container to return a Collection
of Phone EJBs (invoking the getPhoneNumbers()
method), it will query the PHONE
table for all the
records with a foreign key matching the Customer EJB’s primary
key. The use of reverse pointers in this type of relationship is
illustrated in Figure 7-6.
This database schema illustrates that the structure and relationships of the actual database can differ from the relationships as defined in the abstract programming model. In this case the tables are set up in reverse, but the EJB container system will manage the beans to meet the specification of the bean developer. This isn’t always possible; sometimes the database schema is incompatible with a desired relationship field. however, when you are dealing with legacy databases (i.e., databases that were established before the EJB application) reverse-pointer scenarios like the one illustrated here are common, so supporting this kind of relationship mapping is important.
A simpler implementation of the Customer-Phone relationship could use
a link table that maintains two columns with foreign keys pointing to
both the CUSTOMER
and PHONE
records. We could then place a unique constraint on the
PHONE
foreign key column in the link table to
ensure that it contains only unique entries (i.e., that every phone
has only one customer), while allowing the
CUSTOMER
foreign key column to contain duplicates.
The advantage of the link table is that it doesn’t impose the
relationship between the CUSTOMER
and
PHONE
records onto either of the tables.
In the abstract programming model, we represent
multiplicity by
defining a relationship field that can point to many entity beans. To
do this, we employ the same abstract accessor methods we used for
one-to-one relationships, but this time we set the field type to
either java.util.Collection
or
java.util.Set
. The Collection
maintains a homogeneous group of local EJB object references, which
means it contains many references to one kind of entity bean. The
Collection
type may contain duplicate references
to the same entity bean, while the Set
type may
not.
For example, a Customer EJB may have relationships with several phone numbers (e.g., a home phone, work phone, cell phone, fax, etc.), each represented by a Phone EJB. Instead of having a different relationship field for each of these Phone EJBs, the Customer EJB keeps all the Phone EJBs in a collection-based relationship field, which can be accessed through abstract accessor methods:
public abstract class CustomerBean implements javax.ejb.EntityBean { ... // relationship fieldspublic abstract java.util.Collection getPhoneNumbers();
public abstract void setPhoneNumbers(java.util.Collection phones);
public abstract AddressLocal getHomeAddress();() public abstract void setHomeAddress(AddressLocal local); ...
The Phone EJB, like other entity beans, has a bean class and local interface, as shown in the next listing. Notice that the PhoneBean doesn’t provide a relationship field for the Customer EJB. It’s a unidirectional relationship; the Customer maintains a relationship with many Phone EJBs, but the Phone EJBs do not maintain a relationship field back to the Customer. Only the Customer EJB is aware of the relationship:
// the local interface for the Phone EJB public interface PhoneLocal extends javax.ejb.EJBLocalObject { public String getNumber(); public void setNumber(String number); public byte getType(); public void setType(byte type); } // the bean class for the Phone EJB public abstract class PhoneBean implements javax.ejb.EntityBean { public Integer ejbCreate(String number, byte type) { setNumber(number); setType(type); return null; } public void ejbPostCreate(String number,byte type) { } // persistence fields public abstract Integer getId(); public abstract void setId(Integer id); public abstract String getNumber(); public abstract void setNumber(String number); public abstract byte getType(); public abstract void setType(byte type); // standard callback methods ... }
To illustrate how an entity bean uses a collection-based relationship
field, we will define a method in the CustomerBean
class that allows remote clients to add new phone numbers. The
method,
addPhoneNumber()
, uses the phone number arguments
to create a new Phone EJB and then add that Phone EJB to a
collection-based relationship field named
phoneNumbers
:
public abstract class CustomerBean implements javax.ejb.EntityBean { // business methods public void addPhoneNumber(String number, String type) { InitialContext jndiEnc = new InitialContext(); PhoneHomeLocal phoneHome = jndiEnc.lookup("PhoneHomeLocal"); PhoneLocal phone = phoneHome.create(number,type); Collection phoneNumbers = this.getPhoneNumbers(); phoneNumbers.add(phone); } ... // relationship fields public abstract java.util.Collection getPhoneNumbers(); public abstract void setPhoneNumbers(java.util.Collection phones); ...
Note that we created the Phone EJB first, then
added it to the phoneNumbers
collection-based
relationship. We obtained the phoneNumbers
Collection
object from the
getPhoneNumbers()
accessor method, then added the
new Phone EJB to the Collection
just as we would
add any object to a Collection
. Adding the Phone
EJB to the Collection
causes the EJB container to
set the foreign key on the new PHONE
record so
that it points back to the Customer EJB’s
CUSTOMER
record. If we had used a link table, a
new link record would have been created. From this point forward, the
new Phone EJB will be available from the
phoneNumbers
collection-based relationship.
You can also update or remove references in a collection-based
relationship field from the relationship using the relationship field
accessor method. For example, the following code defines two methods
in the CustomerBean
class that allow clients to
remove or update phone numbers in the bean’s
phoneNumbers
relationship field:
public abstract class CustomerBean implements javax.ejb.EntityBean { // business methodspublic void removePhoneNumber(byte typeToRemove) {
Collection phoneNumbers = this.getPhoneNumbers();
Iterator iterator = phoneNumbers.iterator(); while(iterator.hasNext()) { PhoneLocal phone = (PhoneLocal)iterator.next(); if(phone.getType() = typeToRemove) {iterator.remove(phone);
break; } }}
public void updatePhoneNumber(String number,byte typeToUpdate) {
Collection phoneNumbers = this.getPhoneNumbers();
Iterator iterator = phoneNumbers.iterator(); while(iterator.hasNext()) { PhoneLocal phone = (PhoneLocal)iterator.next(); if(phone.getType() = typeToUpdate) {phone.setNumber(number);
break; } }}
... // relationship fieldspublic abstract java.util.Collection getPhoneNumbers();
public abstract void setPhoneNumbers(java.util.Collection phones);
In the removePhoneNumber() business method, a Phone EJB with the matching type was found and then removed from the collection-based relationship. The phone number is not deleted from the database, it’s just disassociated from the Customer EJB (i.e., it is no longer referenced by a Customer). Figure 7-7 shows what happens when a Phone EJB reference is removed from the collection-based relationship.
The updatePhoneNumber() method actually modifies an existing Phone EJB, changing its state in the database. The Phone EJB is still referenced by the collection-based relationship, but its data has changed.
The removePhoneNumber() and updatePhoneNumber() methods illustrate that a collection-based relationship can be accessed and updated just like any other Collection object. In addition, a java.util.Iterator can be obtained from the Collection object for looping operations. However, you should exercise caution when using an iterator over a collection-based relationship. You must not add elements to or remove elements from the Collection object while you are using its Iterator. The only exception to this rule is that the Iterator.remove() method may be called to remove an entry. Although the Collection.add() and Collection.remove() methods can be used in other circumstances, calling these methods while an iterator is in use will result in a java.util.IllegalStateException exception.
If no beans have been added to the phoneNumbers relationship field, the getPhoneNumbers() method will return an empty Collection object. <multiplicity> relationship fields never return null. The Collection object used with the relationship field is implemented by the container system, proprietary to the vendor, and tightly coupled with the inner workings of the container. This allows the EJB container to implement performance enhancements such as lazy loading or optimistic concurrency seamlessly, without exposing those proprietary mechanisms to the bean developer.[27] Application-defined Collection objects may be used with container-manager relationship fields only if the elements are of the proper type. For example, it is legal to create a new Collection object and then add that Collection object to the Customer EJB using the setPhoneNumbers() method:
public void addPhoneNumber(String number, String type) {
... PhoneLocal phone = phoneHome.create(number,type); Collection phoneNumbers = java.util.Vector(); phoneNumbers.add(phone);// This is allowed
this.setPhoneNumbers(phoneNumbers);}
// relationship fieldspublic abstract java.util.Collection getPhoneNumbers();
public abstract void setPhoneNumbers(java.util.Collection phones);
We have used the getPhoneNumbers() method extensively but have not yet used the setPhoneNumbers() method. In most cases this method will not be used, because it updates an entire collection of phone numbers. However, it can be useful for exchanging like relationships between entity beans.
If two Customer EJBs want to exchange phone numbers, they can do so in a variety of ways. The most important thing to keep in mind is that a Phone EJB, as the subject of a one-to-many unidirectional relationship, may reference only one Customer EJB. It can be copied, so that both Customers have Phone EJBs with similar data, but the Phone EJB itself cannot be shared.
Imagine, for example, that Customer A wants to transfer all of its phone numbers to Customer B. It can accomplish this by using Customer B’s setPhoneNumbers() method, as shown in the following listing (we assume the Customer EJBs are interacting through their local interfaces):
CustomerLocal customerA = ... get Customer A CustomerLocal customerB = ... get Customer B Collection phonesA = customerA.getPhoneNumbers(); customerB.setPhoneNumbers( phonesA ); if( customerA.getPhoneNumbers().isEmpty()) // this will be true if( phonesA.isEmpty()) ) // this will be true
As Figure 7-8 illustrates, passing one
collection-based relationship to another actually disassociates those
relationships from the first bean and associates them with the
second. In addition, if the second bean already has a
Collection
of Phone EJBs in its
phoneNumbers
relationship field, those beans are
bumped out of the relationship and disassociated from the bean.
The result of this exchange may be counterintuitive, but it is
necessary to uphold the multiplicity of the relationship, which says
that the Phone EJB may have only one Customer EJB. This explains why
Phone EJBs 1, 2, and 3 don’t reference both Customers A and B,
but it doesn’t explain why Phone EJBs 4, 5, and 6 are
disassociated from Customer B. Why isn’t Customer B associated
with all the Phone EJBs? The reason is purely a matter of semantics,
since the relational database schema wouldn’t technically
prevent this from occurring. The act of replacing one
Collection
with another by calling
setPhoneNumbers(Collection
collection)
implies that Customer B’s
initial Collection
object is no longer referenced.
In addition to moving whole collection-based relationships between beans, it is possible to move individual Phone EJBs between Customers.These cannot be shared either. For example, if a Phone EJB aggregated by Customer A is added to the relationship collection of Customer B, that Phone EJB changes so that it’s now referenced by Customer B instead of Customer A, as Figure 7-9 illustrates.
One again, it’s the multiplicity of the relationship that prevents Phone 1 from referencing both Customer A and Customer B.
The abstract persistence schema for one-to-many unidirectional
relationships has a few significant differences from the
<ejb-relation>
elements seen so far:
<relationships> <ejb-relation> <ejb-relation-name>Customer-Phones</ejb-relation-name> <ejb-relationship-role> <ejb-relationship-role-name> Customer-has-many-Phone-numbers </ejb-relationship-role-name> <multiplicity>One
</multiplicity> <relationship-role-source> <ejb-name>CustomerEJB
</ejb-name> </relationship-role-source><cmr-field>
<cmr-field-name>phoneNumbers</cmr-field-name>
<cmr-field-type>java.util.Collection</cmr-field-type>
</cmr-field>
</ejb-relationship-role> <ejb-relationship-role> <ejb-relationship-role-name> Phone-belongs-to-Customer </ejb-relationship-role-name> <multiplicity>Many
</multiplicity> <relationship-role-source> <ejb-name>PhoneEJB
</ejb-name> </relationship-role-source> </ejb-relationship-role> </ejb-relation> </relationships>
In the <ejb-relation>
element, the
multiplicity for the Customer EJB is declared as
One
, while the multiplicity for the Phone
EJB’s <ejb-relationship-role>
is
Many
. This obviously establishes the relationship
as one-to-many. The fact that the
<ejb-relationship-role>
for the Phone EJB
doesn’t specify a <cmr-field>
element
indicates that the one-to-many relationship is unidirectional; the
Phone EJB doesn’t contain a reciprocating reference to the
Customer EJB.
The most interesting change is the addition of the
<cmr-field-type>
element in the Customer
EJB’s <cmr-field>
declaration. The
<cmr-field-type>
must be specified for a
bean that has a collection-based relationship field (in this case,
the phoneNumbers
field maintained by the Customer
EJB). The <cmr-field-type>
can have one of
two values, java.util.Collection
or
java.util.Set
, which are the allowed
collection-based relationship types. In a future specification, the
allowed types for collection-based relationships may be expanded to
include java.util.List
and
java.util.Map
, but these are not yet
supported.
Please refer to Workbook Exercise 7.1, Entity Relationships in CMP 2.0: Part 1. This workbook is available free, in PDF format, at http://www.oreilly.com/catalog/entjbeans3/workbooks.
To make things more interesting, we are going to introduce some more entity beans so that we can model the remaining four relationships: many-to-one unidirectional, one-to-many bidirectional, many-to-many bidirectional, and many-to-many unidirectional.
In Titan’s reservation system, every customer (a.k.a. passenger) can be booked on one or more cruises. Each booking requires a reservation. A reservation may be for one or more (usually two) passengers. Each cruise requires exactly one ship, but each ship may be used for many cruises throughout the year. Figure 7-10 illustrates these relationships.
The relationships investigated in the next four sections will each refer back to the above diagram.
Many-to-one unidirectional relationships result when many entity beans reference a single entity bean, but the referenced entity bean is unaware of the relationship. In the Titan Cruise business, for example, the concept of a cruise can be captured by a Cruise EJB. As shown in Figure 7-10, each Cruise has a many-to-one relationship with a Ship. This relationship is unidirectional; the Cruise EJB maintains a relationship with the Ship EJB, but the Ship EJB does not keep track of the Cruises for which it is used.
The relational database schema for the Cruise-to-Ship relationship is
fairly simple; it requires that the CRUISE
table
maintain a foreign key column for the SHIP
table,
with each row in the CRUISE
table pointing to a
row in the SHIP
table. The
CRUISE
and SHIP
tables are
defined below; Figure 7-11 shows the relationship
between these tables in the database.
An enormous amount of data would be required to adequately describe
an ocean liner, but we’ll use a simple definition of the
SHIP
table here:
CREATE TABLE SHIP ( ID INT PRIMARY KEY NOT NULL, NAME CHAR(30), TONNAGE DECIMAL (8,2) }
The CRUISE
table maintains data on each
cruise’s name, ship, and other information that is not germane
to this discussion. (Other tables, such as
RESERVATIONS
, SCHEDULES
, and
CREW
, would have relationships with the
CRUISE
table through linking tables.) We’ll
keep it simple and focus on a definition that is useful for the
examples in this book:
CREATE TABLE CRUISE ( ID INT PRIMARY KEY NOT NULL, NAME CHAR(30), SHIP_ID INT }
In the abstract programming model, the relationship field is of type
ShipLocal
and is maintained by the Cruise EJB. The
abstract accessor methods are similar to those defined in the
previous examples:
public abstract class CruiseBean implements javax.ejb.EntityBean { public Integer ejbCreate(String name, ShipLocal ship) { setName(name); return null; } public void ejbPostCreate(String name, ShipLocal ship) { setShip(ship); } public abstract Integer getId(); public abstract void setId(Integer id); public abstract void setName(String name); public abstract String getName();public abstract void setShip(ShipLocal ship);
public abstract ShipLocal getShip();
// EJB callback methods ... }
Notice that the Cruise EJB requires that a
ShipLocal
reference be passed as an argument when
the Cruise is created; this is perfectly natural, since a cruise
cannot exist without a ship. According to the EJB 2.0 specification,
relationship fields cannot be modified or set in the
ejbCreate()
method. They must be modified in the
ejbPostCreate()
, a constraint that is followed in
the CruiseBean
class.
The reasons relationships are set in
ejbPostCreate()
and not ejbCreate()
are simple: the primary key for the entity bean may not be available
until after ejbCreate()
executes. The primary key
is needed if the mapping for the relationship uses the key as a
foreign key, so assignment of relationships is postponed until the
ejbCreate()
method completes and the primary
key becomes available. This is also true with autogenerated primary
keys, which usually require that the insert be done before a primary
key can be generated. In addition, referential integrity may specify
non-null foreign keys in referencing tables, so the insert must take
place first. In reality, the transaction does not complete until both
the ejbCreate()
and
ejbPostCreate()
methods have executed, so the
vendors are free to choose the best time for database inserts and
linking of relationships.
The relationship between the Cruise and Ship EJBs is unidirectional, so the Ship EJB doesn’t define any relationship fields, just persistence fields:
public abstract class ShipBean implements javax.ejb.EntityBean { public Integer ejbCreate(Integer primaryKey,String name,double tonnage) { setId(primaryKey); setName(name); setTonnage(tonnage); return null; } public void ejbPostCreate(Integer primaryKey,String name,double tonnage) { } public abstract void setId(Integer id); public abstract Integer getId(); public abstract void setName(String name); public abstract String getName(); public abstract void setTonnage(double tonnage); public abstract double getTonnage(); // EJB callback methods ... }
This should all be fairly mundane for you now. The impact of exchanging Ship references between Cruise EJBs should be equally obvious. As shown previously in Figure 7-10, each Cruise may reference only a single Ship, but each Ship may reference many Cruise EJBs. If you take Ship A, which is referenced by Cruise 1, and pass it to Cruise 4, both Cruise 1 and 4 will reference Ship A, as shown in Figure 7-12.
The abstract persistence schema is simple in a many-to-one unidirectional relationship. It uses everything you have already learned, and shouldn’t contain any surprises:
<ejb-jar> ... <enterprise-beans> <entity> <ejb-name>CruiseEJB
</ejb-name> <local-home>com.titan.cruise.CruiseHomeLocal</local-home> <local>com.titan.cruise.CruiseLocal</local> ... </entity> <entity> <ejb-name>ShipEJB
</ejb-name> <local-home>com.titan.ship.ShipHomeLocal</local-home> <local>com.titan.ship.ShipLocal</local> ... </entity> ... </enterprise-beans> <relationships><ejb-relation>
<ejb-relation-name>Cruise-Ship</ejb-relation-name> <ejb-relationship-role> <ejb-relationship-role-name> Cruise-has-a-Ship </ejb-relationship-role-name> <multiplicity>Many
</multiplicity> <relationship-role-source> <ejb-name>CruiseEJB
</ejb-name> </relationship-role-source> <cmr-field> <cmr-field-name>ship</cmr-field-name> </cmr-field> </ejb-relationship-role> <ejb-relationship-role> <ejb-relationship-role-name> Ship-has-many-Cruises </ejb-relationship-role-name> <multiplicity>One
</multiplicity> <relationship-role-source> <ejb-name>ShipEJB
</ejb-name> </relationship-role-source> </ejb-relationship-role></ejb-relation>
</relationships>
The <ejb-relationship-role>
of the Cruise
EJB defines its multiplicity as Many
and declares
ship
as its relationship field. The
<ejb-relationship-role>
of the Ship EJB
defines its multiplicity as One
and contains no
<cmr-field>
declaration, because it’s
a unidirectional relationship.
One-to-many and many-to-one bidirectional relationships are the same thing, so they are both covered in this section. A one-to-many bidirectional relationship occurs when one entity bean maintains a collection-based relationship field with another entity bean, and each entity bean referenced in the collection maintains a single reference back to its aggregating bean. For example, in the Titan Cruise system, each Cruise EJB maintains a collection of references to all the passenger reservations made for that Cruise, and each Reservation EJB maintains a single reference to its Cruise. The relationship is a one-to-many bidirectional relationship from the perspective of the Cruise EJB, and a many-to-one bidirectional relationship from the perspective of the Reservation EJB.
The first table we need is the RESERVATION
table,
which is defined in the following listing. Notice that the
RESERVATION
table contains, among other things, a
column that serves as a foreign key to the CRUISE
table:
CREATE TABLE RESERVATION
(
ID INT PRIMARY KEY NOT NULL,
AMOUNT_PAID DECIMAL (8,2),
DATE_RESERVED DATE,
CRUISE_ID INT
}
While the RESERVATION
table contains a foreign key
to the CRUISE
table, the CRUISE
table doesn’t maintain a foreign key back to the
RESERVATION
table. The EJB container system can
determine the relationship between the Cruise and Reservations EJBs
by querying the RESERVATION
table, so explicit
pointers from the CRUISE
table to the
RESERVATION
table are not required. This
illustrates once again the separation between the entity bean’s
view of its persistence relationships and the database’s actual
implementation of those relationships.
The relationship between the RESERVATION
and
CRUISE
tables is shown in Figure 7-13.
As an alternative, we could have used a link table that would declare
foreign keys to both the CRUISE
and
RESERVATION
tables. This link table would probably
impose a unique constraint on the RESERVATION
foreign key to ensure that each RESERVATION
record
had only one corresponding CRUISE
record.
To model the relationship between Cruises and Reservations, we’ll first define the Reservation EJB, which maintains a relationship field to the Cruise EJB:
public abstract class ReservationBean implements javax.ejb.EntityBean { public Integer ejbCreate(CruiseLocal cruise) { return null; } public void ejbPostCreate(CruiseLocal cruise) { setCruise(cruise); }public abstract void setCruise(CruiseLocal cruise);
public abstract CruiseLocal getCruise();
public abstract Integer getId(); public abstract void setId(Integer id); public abstract void setAmountPaid(float amount); public abstract float getAmountPaid(); public abstract void setDate(Date date); public abstract Date getDate(); // EJB callback methods ... }
When a Reservation EJB is created, a reference to the Cruise for
which it is created must be passed to the create()
method. Notice that the CruiseLocal
reference is
set in the ejbPostCreate()
method and not the
ejbCreate()
method. As stated previously, the
ejbCreate()
method is not allowed to update
relationship fields; that is the job of the
ejbPostCreate()
method.
We need to add a collection-based relationship field to the Cruise EJB so that it can reference all the Reservation EJBs that were created for it:
public abstract class CruiseBean implements javax.ejb.EntityBean { ...public abstract void setReservations(Collection res);
public abstract Collection getReservations();
public abstract Integer getId(); public abstract void setId(Integer id); public abstract void setName(String name); public abstract String getName(); public abstract void setShip(ShipLocal ship); public abstract ShipLocal getShip(); // EJB callback methods ... }
The interdependency between the Cruise and Reservation EJBs produces some interesting results when you create a relationship between these beans. For example, the act of creating a Reservation EJB automatically adds that entity bean to the collection-based relationship of the Cruise EJB:
CruiseLocal cruise = ... get CruiseLocal reference ReservationLocal reservation = reservationHomeLocal.create( cruise ); Collection collection = cruise.getReservations(); if(collection.contains(reservation)) // always returns true
This is a side effect of the bidirectional relationship. Any Cruise referenced by a specific Reservation has a reciprocal reference back to that Reservation. If Reservation X references Cruise A, Cruise A must have a reference to Reservation X. When you create a new Reservation EJB and set the Cruise reference on that bean, the Reservation is automatically added to the Cruise EJB’s reservation field.[28]
Sharing references between beans has some of the ugly consequences we learned about earlier. For example, passing a collection of Reservations referenced by Cruise A to Cruise B actually moves those relationships to Cruise B, so Cruise A has no more Reservations (see Figure 7-14).
As was the case with the Customer and Phone EJBs (see Figure 7-8), this effect is usually undesirable and should be avoided, as it displaces the set of Reservation EJBs formerly associated with Cruise B.
You can move an entire collection from one bean to another and
combine it with the second bean’s collection by using the
Collection.addAll()
method, as shown in Figure 7-15.[29]
If you move Cruise A’s collection of references to Cruise B,
Cruise A will no longer reference any Reservation EJBs, while Cruise
B will reference those it referenced before the exchange as well as
those it acquired from Cruise A.
The impact of moving an individual Reservation EJB from one Cruise to
another is similar to what we have seen with other one-to-many
relationships: the result is the same as was shown in Figure 7-9, when a Phone was moved from one Customer to
another. It’s interesting to note that the net effect of using
Collection.addAll()
in this scenario is the same
as using Collection.add()
on the target collection
for every element in the source collection. In both cases, you move
every element from the source collection to the target collection.
Once again, container-managed relationship fields, collection-based
or otherwise, must always use the
javax.ejb.EJBLocalObject
(local) interface of a bean and never the
javax.ejb.EJBObject
(remote) interface. It would
be illegal, for example, to try to add the remote interface of the
Reservation EJB (if it has one) to the Cruise EJB’s Reservation
Collection
. Any attempt to add a remote interface
type to a collection-based relationship field will result in a
java.lang.IllegalArgumentException
.
The abstract persistence schema for the Cruise-Reservation
relationship doesn’t introduce any new concepts. The Cruise and
Reservation <ejb-relationship-role>
elements
both have <cmr-field>
elements. The Cruise
specifies One
as its multiplicity, while
Reservation specifies Many
. Here’s the code:
<ejb-jar> ... <enterprise-beans> <entity> <ejb-name>CruiseEJB
</ejb-name> <local-home>com.titan.cruise.CruiseHomeLocal</local-home> <local>com.titan.cruise.CruiseLocal</local> ... </entity> <entity> <ejb-name>ReservationEJB
</ejb-name> <local-home> com.titan.reservations.ReservationHomeLocal </local-home> <local>com.titan.reservation.ReservationLocal</local> ... </entity> ... </enterprise-beans> <relationships><ejb-relation>
<ejb-relation-name>Cruise-Reservation </ejb-relation-name> <ejb-relationship-role> <ejb-relationship-role-name> Cruise-has-many-Reservations </ejb-relationship-role-name> <multiplicity>One
</multiplicity> <relationship-role-source> <ejb-name>CruiseEJB
</ejb-name> </relationship-role-source> <cmr-field> <cmr-field-name>reservations
</cmr-field-name> <cmr-field-type>java.util.Collection
</cmr-field-type> </cmr-field> </ejb-relationship-role> <ejb-relationship-role> <ejb-relationship-role-name> Reservation-has-a-Cruise </ejb-relationship-role-name> <multiplicity>Many
</multiplicity> <relationship-role-source> <ejb-name>ReservationEJB
</ejb-name> </relationship-role-source> <cmr-field> <cmr-field-name>cruise
</cmr-field-name> </cmr-field> </ejb-relationship-role></ejb-relation>
</relationships>
Many-to-many bidirectional relationships occur when many beans maintain a collection-based relationship field with another bean, and each bean referenced in the Collection maintains a collection-based relationship field back to the aggregating beans. For example, in Titan Cruises every Reservation EJB may reference many Customers (a family can make a single reservation) and each Customer can have many reservations (a person may make more than one reservation in a year). This is an example of a many-to-many bidirectional relationship; the customer keeps track of all of its reservations, and each reservation may be for many customers.
The RESERVATION
and CUSTOMER
tables have already been established. In order to establish a
many-to-many bidirectional relationship, the
RESERVATION_CUSTOMER_LINK
table is created. This
table maintains two foreign key columns: one for the
RESERVATION
table and another for the
CUSTOMER
table:
CREATE TABLE RESERVATION_CUSTOMER_LINK ( RESERVATION_ID INT, CUSTOMER_ID INT }
The relationship between the CUSTOMER
,
RESERVATION
, and
CUSTOMER_RESERVATION_LINK
tables is illustrated in
Figure 7-16.
Many-to-many bidirectional relationships always require a link in a normalized relational database.
To model the many-to-many bidirectional relationship between the Customer and Reservation EJBs, we need to modify both bean classes to include collection-based relationship fields:
public abstract class ReservationBean implements javax.ejb.EntityBean { public Integer ejbCreate(CruiseLocal cruise,Collection customers) { return null; } public void ejbPostCreate(CruiseLocal cruise,Collection customers) { setCruise(cruise); Collection myCustomers = this.getCustomers(); myCustomers.addAll(customers); }public abstract void setCustomers(Set customers);
public abstract Set getCustomers();
... }
The abstract accessor methods defined for the
customers
relationship field declare the
Collection type as java.util.Set
. The
Set
type should contain only unique Customer EJBs
and no duplicates. Duplicate Customers would introduce some
interesting but undesirable side effects in Titan’s reservation
system. To maintain a valid passenger count, and to avoid
overcharging customers, Titan requires that a Customer be booked only
once in the same Reservation. The Set
collection
type expresses this restriction. The effectiveness of the
Set
collection type depends largely on
referential-integrity constraints established in the underlying
database.
In addition to adding the
getCustomers()
/setCustomers()
abstract accessors, we have modified the
ejbCreate()
/ejbPostCreate()
methods to take a Collection
of Customer EJBs.
When a Reservation EJB is created, it must be provided with a list of
Customer EJBs that it will add to its own Customer EJB collection. As
is always the case, container-managed relationship fields cannot be
modified in the ejbCreate()
method. It’s the
ejbPostCreate()
method’s job to modify
container-managed relationships fields when a bean is created.
We have also modified the Customer EJB to allow it to maintain a
collection-based relationship with all of its Reservations. While the
idea of a Customer having multiple Reservations may seem odd,
it’s possible for someone to book more than one cruise in
advance. To allow for this possibility, we have enhanced the Customer
EJB to include a reservations
relationship field:
public abstract class CustomerBean implements javax.ejb.EntityBean { ... // relationship fieldspublic abstract void setReservations(Collection reservations);
public abstract Collection getReservations();
...
When a Reservation EJB is created, it is passed references to its
Cruise and to a collection of Customers. Because the relationship is
defined as bidirectional, the EJB container will automatically add
the Reservation EJB to the reservations
relationship field of the Customer EJB. The following code fragment
illustrates this:
Collection customers = ... get local Customer EJBs CruiseLocal cruise = ... get a local Cruise EJB ReservationHomeLocal resHome = ... get local Reservation homeReservationLocal myReservation = resHome.create(cruise, customers);
Iterator iterator = customers.iterator(); while(iterator.hasNext()) { CustomerLocal customer = (CustomerLocal)iterator.next(); Collection reservations = customer.getReservations();if( reservations.contains( myReservation ))
// this will always be true
}
Exchanging bean references in many-to-many bidirectional relationships results in true sharing, where each relationship maintains a reference to the transferred collection. This type of relationship is illustrated in Figure 7-17.
Of course, using the
setCustomers()
or
setReservations()
method will end up changing the references
between the entity bean and the elements in the original collection,
but the other relationships held by those elements are unaffected.
Figure 7-18 illustrates what happens when an entire
collection is shared in a many-to-many bidirectional relationship.
After the setCustomers()
method is invoked on
Reservation D, Reservation D’s Customers change to Customers 1,
2, and 3. Customers 1, 2, and 3 were also referenced by Reservation A
before the sharing operation and remain referenced by Reservation A
after it’s complete. In fact, only the relationships between
Reservation D and Customers 4, 5, and 6 are impacted. The
relationship between Customers 4, 5, and 6 and other Reservation EJBs
are not affected by the sharing operation. This is a unique property
of many-to-many relationships (both bidirectional and
unidirectional); operations on the relationship fields affect only
those specific relationships, they do not impact either party’s
relationships with other beans of the same relationship type.
The abstract persistence schema of a many-to-many bidirectional
relationship introduces nothing new and should contain no surprises.
Each <ejb-relationship-role>
specifies
Many
as its multiplicity and declares a
<cmr-field>
of a specific
Collection
type:
<ejb-jar> ... <enterprise-beans> <entity> <ejb-name>CustomerEJB
</ejb-name> <local-home>com.titan.customer.CustomerHomeLocal</local-home> <local>com.titan.customer.CustomerLocal</local> ... </entity> <entity> <ejb-name>ReservationEJB
</ejb-name> <local-home> com.titan.reservation.ReservationHomeLocal</local-home> <local>com.titan.reservation.ReservationLocal</local> ... </entity> ... </enterprise-beans> <relationships><ejb-relation>
<ejb-relation-name>Customer-Reservation</ejb-relation-name> <ejb-relationship-role> <ejb-relationship-role-name> Customer-has-many-Reservations </ejb-relationship-role-name> <multiplicity>Many
</multiplicity> <relationship-role-source> <ejb-name>CustomerEJB
</ejb-name> </relationship-role-source> <cmr-field> <cmr-field-name>reservations
</cmr-field-name> <cmr-field-type>java.util.Collection
</cmr-field-type> </cmr-field> </ejb-relationship-role> <ejb-relationship-role> <ejb-relationship-role-name> Reservation-has-many-Customers </ejb-relationship-role-name> <multiplicity>Many
</multiplicity> <relationship-role-source> <ejb-name>ReservationEJB
</ejb-name> </relationship-role-source> <cmr-field> <cmr-field-name>customers
</cmr-field-name> <cmr-field-type>java.util.Set
</cmr-field-type> </cmr-field> </ejb-relationship-role></ejb-relation>
</relationships>
Many-to-many unidirectional
relationships occur when many beans maintain a collection-based
relationship with another bean, but the bean referenced in the
Collection
does not maintain a collection-based
relationship back to the aggregating beans. In Titan’s
reservation system, every Reservation is assigned a Cabin on the
Ship. This allows a Customer to reserve a specific Cabin (e.g., a
deluxe suite or a cabin with sentimental significance) on the Ship.
In this case, each Reservation may be for more than one Cabin, since
each Reservation can be for more than one Customer. An example is a
family that makes a Reservation for five people for two adjacent
Cabins (one for the kids and the other for the parents).
While the Reservation must keep track of the Cabins it reserves, it’s not necessary for the Cabins to track all the Reservations made by all the Cruises. The Reservation EJBs reference a collection of Cabin beans, but the Cabin beans do not maintain references back to the Reservations.
Our first order of business is to declare a CABIN
table:
CREATE TABLE CABIN ( ID INT PRIMARY KEY NOT NULL, SHIP_ID INT, NAME CHAR(10), DECK_LEVEL INT, BED_COUNT INT }
Notice that the CABIN
table maintains a foreign
key to the SHIP
table. While this relationship is
important, we won’t explore it here because we’ve already
covered the one-to-many bidirectional relationship in this chapter.
The Cabin-Ship relationship is included in Figure 7-10, however, for completeness. To accommodate the
many-to-many unidirectional relationship between the
RESERVATION
and CABIN
table, we
will need a RESERVATION_CABIN_LINK
table:
CREATE TABLE RESERVATION_CABIN_LINK ( RESERVATION_ID INT, CABIN_ID INT }
The relationship between the CABIN
records and the
RESERVATION
records through the
RESERVATION_CABIN_LINK
table is illustrated in
Figure 7-19.
To model this relationship, we need to add a collection-based relationship field for Cabin beans to the Reservation EJB:
public abstract class ReservationBean implements javax.ejb.EntityBean { ...public abstract void setCabins(Set cabins);
public abstract Set getCabins();
... }
In addition, we need to define a Cabin bean. Notice that the Cabin bean doesn’t maintain a relationship back to the Reservation EJB. The lack of a container-managed relationship field for the Reservation EJB tells us the relationship is unidirectional:
public abstract class CabinBean implements javax.ejb.EntityBean { public Integer ejbCreate(ShipLocal ship, String name) { this.setName(name); return null; } public void ejbPostCreate(ShipLocal ship, String name) { this.setShip(ship); } public abstract void setShip(ShipLocal ship); public abstract ShipLocal getShip(); public abstract Integer getId(); public abstract void setId(Integer id); public abstract void setName(String name); public abstract String getName(); public abstract void setBedCount(int count); public abstract int getBedCount(); public abstract void setDeckLevel(int level); public abstract int getDeckLevel(); // EJB callback methods }
Although the Cabin bean doesn’t define a relationship field for the Reservation EJB, it does define a one-to-many bidirectional relationship for the Ship EJB.
The effect of exchanging relationship fields in a many-to-many
unidirectional relationship is basically the same as that in a
many-to-many bidirectional relationship. Use of the
Collection.addAll()
operation to share entire
collections has the same net effect we noted in the previous section
on many-to-many bidirectional relationships. The only difference is
that the arrows point only one way, instead of both ways.
If a Reservation removes a Cabin bean from its collection-based relationship field, the operation doesn’t affect other Reservation EJBs that reference that Cabin bean. This is illustrated in Figure 7-20.
The abstract persistence schema for the Reservation-Cabin
relationship holds no surprises. The multiplicity of both
<ejb-relationship-role>
elements is
Many
, but only the Reservation EJB’s
<ejb-relationship-role>
defines a
<cmr-field>
:
<ejb-jar> ... <enterprise-beans> <entity> <ejb-name>CabinEJB
</ejb-name> <local-home>com.titan.cabin.CabinHomeLocal</local-home> <local>com.titan.cabin.CabinLocal</local> ... </entity> <entity> <ejb-name>ReservationEJB
</ejb-name> <local-home> com.titan.reservation.ReservationHomeLocal</local-home> <local>com.titan.reservation.ReservationLocal</local> ... </entity> ... </enterprise-beans> <relationships><ejb-relation>
<ejb-relation-name>Cabin-Reservation</ejb-relation-name> <ejb-relationship-role> <ejb-relationship-role-name> Cabin-has-many-Reservations </ejb-relationship-role-name> <multiplicity>Many
</multiplicity> <relationship-role-source> <ejb-name>CabinEJB
</ejb-name> </relationship-role-source> </ejb-relationship-role> <ejb-relationship-role> <ejb-relationship-role-name> Reservation-has-many-Customers </ejb-relationship-role-name> <multiplicity>Many
</multiplicity> <relationship-role-source> <ejb-name>ReservationEJB
</ejb-name> </relationship-role-source> <cmr-field> <cmr-field-name>cabins
</cmr-field-name> <cmr-field-type>java.util.Set
</cmr-field-type> </cmr-field> </ejb-relationship-role></ejb-relation>
</relationships>
Please refer to Workbook Exercise 7.2, Entity Relationships in CMP 2.0: Part 2. This workbook is available free, in PDF format, at http://www.oreilly.com/catalog/entjbeans3/workbooks.
Only entity beans that are deployed together with the same deployment descriptor can have relationships with each other. When deployed together, the entity beans are seen as a single deployment unit or application, in which all the entities are using the same database and are co-located in the same JVM. This restriction makes it possible for the EJB container system to use lazy loading, optimistic concurrency, and other performance optimizations. While it would technically be possible to support relationships across deployments or even across container systems, the difficulty of doing so, combined with the expected degradation in performance, was reason enough to limit the relationship fields to those entity beans that are deployed together. In the future, entity relationships may be expanded to include remote references to entities deployed in other containers or other JAR files in the same container, but remote references are not allowed as relationship types in EJB 2.0.
As
you learned in Chapter 5, invoking the
remove()
operation on the EJB home or EJB object
of an entity bean deletes that entity bean’s data from the
database. Deleting the bean’s data, of course, has an impact on
the relationships that entity bean has with other entity beans.
When an entity bean is deleted, the EJB container first removes it from any relationships it maintains with other entity beans. Consider, for example, the relationship between the entity beans we have created in this chapter (shown in Figure 7-21).
If an EJB application invokes remove()
on a
CreditCard EJB, the Customer EJB that referenced that bean would now
have a value of null
for its
creditCard
relationship field, as the following
code fragment illustrates:
CustomerLocal customer = ... get Customer EJB
CreditCardLocal creditCard = customer.getCreditCard();
creditCard.remove();
if(customer.getCreditCard() == null)
// this will always be true
The moment the remove()
operation is invoked on
the CreditCard EJB’s local reference, the bean is disassociated
from the Customer bean and deleted. The impact of removing a bean is
even more interesting when that bean participates in several
relationships. For example, invoking remove()
on a
Customer EJB will impact the relationship fields of the Reservation,
Address, Phone, and CreditCard EJBs. With single EJB object
relationship fields, such as the CreditCard EJB’s reference to
the Customer EJB, the field will be set to null
for the entity bean that was removed. With collection-based
relationship fields, the entity that was deleted will be removed from
the collection. In some cases, you want the removal of an entity bean
to cause a cascade of deletions. For example, if a Customer EJB is
removed, we would want the Address EJBs referenced in its
billingAddress
and homeAddress
relationship field to be deleted. This would avoid the problem of
disconnected Address EJBs in the database. The
<cascade-delete>
element requests cascade
delete; it can be used with one-to-one or one-to-many relationships.
It does not make sense in many-to-many and many-to-one relationships,
because of the nature of those relationships. For example, in the
many-to-one relationship between the Reservation and Cruise EJBs,
cancellation of a reservation by one passenger should not cancel the
cruise itself! In other words, we would not want the deletion of a
Reservation EJB to cause the deletion of its Cruise EJB.
Here’s how to modify the relationship declaration for the Customer and Address EJBs to obtain a cascade delete:
<relationships>
<ejb-relation>
<ejb-relationship-role>
<multiplicity>One</multiplicity>
<relationship-role-source>
<ejb-name>CustomerEJB</ejb-name>
</relationship-role-source>
<cmr-field>
<cmr-field-name>homeAddress</cmr-field-name>
</cmr-field>
</ejb-relationship-role>
<ejb-relationship-role>
<multiplicity>One</multiplicity>
<cascade-delete/>
<relationship-role-source>
<ejb-name>AddressEJB</ejb-name>
</relationship-role-source>
</ejb-relationship-role>
</ejb-relation>
</relationships>
If you do not specify a cascade delete, the
ADDRESS
record associated with the Address EJB
will not be removed when the CUSTOMER
record is
deleted. This can result in a disconnected dependent object value,
which means that the data is not linked to anything. In some cases,
we want to specify a cascading delete to ensure that no detached
entities remain after a bean is removed. In other cases, however, we
do not want to use a cascading delete. If, for example, the
ADDRESS
record associated with an entity bean is
shared by other CUSTOMER
records (i.e., if two
different customers reside at the same residence), we probably do not
want it to be deleted when the CUSTOMER
record is
deleted. A cascade delete can be specified only on an entity bean
that has a single reference to the entity being deleted. For example,
you can specify a cascade delete in the
<ejb-relationship-role>
for the Phone EJB in
the Customer-Phone relationship if the Customer is deleted, because
each Phone EJB is referenced by only one Customer. However, you
cannot specify a cascade delete for the Customer EJB in this
relationship, because a Customer maybe referenced by many Phone EJBs.
The entity bean that causes the cascade delete must have a
multiplicity of One
in the relationship.
A cascade delete affects only the relationship for which it is specified. So, for example, if you specify a cascade delete for the Customer-Phone relationship but not the Customer-HomeAddress relationship, deleting a Customer will cause all the Phone EJBs to be deleted, but not the Address EJBs. You must also specify a cascade delete for the Address EJBs if you want them to be deleted.
Cascade delete can propagate through relationships in a chain reaction. For example, if the Ship-Cruise relationship specifies a cascade delete on the Cruise relationship field and the Cruise-Reservation relationship specifies a cascade delete on the Reservation relationship field, when a Ship is removed all of its Cruises and the Reservations for those Cruises will be removed.
Cascade delete is a powerful tool, but it’s also dangerous and should be handled with care. The effectiveness of a cascade delete depends in large part on the referential integrity of the database. For example, if the database is set up so that a foreign key must point to an existing record, deleting an entity’s data could violate that restriction and cause a transaction rollback.
Please refer to Workbook Exercise 7.3, Cascade Deletes in CMP 2.0. This workbook is available free, in PDF format, at http://www.oreilly.com/catalog/entjbeans3/workbooks.
[27] A Collection from a collection-based relationship that is materialized in a transaction cannot be modified outside the scope of that transaction. See Chapter 14 for more details.
[28] This actually depends in large part on the sequence of operations, the transaction context, and even the isolation levels used in the database. Chapter 14 provides more information on these topics.
[29] The
addAll()
method must be supported by
collection-based relationship fields in EJB 2.0.