Chapter 6 covered basic container-managed persistence (CMP), including container-managed persistence fields and an introduction to a basic container-managed relationship field. This chapter develops the Customer EJB and discusses the seven relationships that entity beans can have with each other.
In order to model real-world business concepts, entity beans must be capable of forming complex relationships. Chapter 6 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 possible. For example, each Address could also reference its Customer, a bidirectional, one-to-one relationship in which both participants maintain references to each other. Entity beans can also have one-to-many, many-to-one, and many-to-many relationships. For example, the Customer EJB may have many phone numbers, but each phone number belongs to only one Customer (a one-to-many relationship). A Customer may have been on many Cruises, and each Cruise has many Customers (a many-to-many relationship).
Seven types of relationships can exist between EJBs. There are four types of cardinality: one-to-one, one-to-many, many-to-one, and many-to-many. In addition, each relationship can be either unidirectional or bidirectional. These options seem to yield 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 relationships, it helps to think about some simple examples:
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 particular cruise, you want to be able to look up all reservations. (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 with it, although 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, 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 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 of 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 step is handled in the <relationships>
section of the XML deployment descriptor. As we discuss each type of
relationship, 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 specification, the term “abstract persistence schema” actually refers to both the Java idioms and the XML elements, but this book separates 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.
<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 an
<ejb-relation>
element for each
entity-to-entity relationship. For each set of abstract accessor
methods that define a relationship field, there must be an
<ejb-relation>
element in the deployment
descriptor. EJB requires the entity beans that participate in a
relationship to be defined in the same XML deployment descriptor.
Here is a partial listing of the deployment descriptor for the Customer and Address EJBs, with 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>
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
EJB’s <multiplicity>
element would be set to Many
.
In Chapter 6, the Customer EJB had 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 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
. Returning a collection is
discussed later in this chapter.
Now that we have established a basic understanding of how elements are declared, we are ready to discuss each of the seven types of relationships. 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
demonstrate possible relationships between entities in the database;
they don’t represent the only way to implement these
relationships, or even the best way. For example, the
Address-Customer relationship is implemented 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
. The difference really
isn’t important for the purposes of this book, as
EJB’s container-managed persistence can support
different database organizations. If you have the luxury of defining
your own database schema, organize your database in whatever way
makes the most sense for your application. If you’ve
inherited a database schema, container-managed persistence should be
flexible enough to support the database organization you already
have.
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 one between the Customer EJB and the Address EJB defined in Chapter 6. In this example, 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. The relationship is therefore unidirectional—you can only go from the Customer to the Address, 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 the “Many-to-Many,
Unidirectional Relationship” section.
In unidirectional relationships (navigated only one way), only one of
the enterprise beans defines abstract accessor methods that let it
get or set the other bean in the relationship. 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 abstract 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),
HAS_GOOD_CREDIT INT,
HOME_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 a 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 maintained 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), HAS_GOOD_CREDIT INT, HOME_ADDRESS_ID INT, BILLING_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. Using a linking table 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. The abstract persistence schema of an entity bean may map to a variety of database schemas; the database schemas used in these examples are only a few possiblities.
To model the relationship between the Customer and CreditCard EJBs,
we need to declare a relationship field named
customer
in the CreditCardBean
class:
public abstract class CreditCardBean extends javax.ejb.EntityBean { ... // relationship fields public 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 ... }
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).
We can also add a set of abstract accessor methods in the
CustomerBean
class for the
creditCard
relationship field:
public abstract 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
automatically establishes the customer relationship on the CreditCard
EJB to point back to the Customer EJB:
public abstract class CustomerBean implements javax.ejb.EntityBean { ... // The setCreditCard( ) business method uses the setCreditCard( ) abstract accessor 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 automatically this.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 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 simple one-to-one relationships—or, for that matter, from multiple one-to-one relationships with the same type of bean. 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 implemented 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 queries 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. 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 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 fields public abstract Collection getPhoneNumbers( ); public abstract void setPhoneNumbers(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 { final public static byte HOME_PHONE = (byte)1; final public static byte WORK_PHONE = (byte)2; final public static byte CELL_PHONE = (byte)3; 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 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, byte type) { InitialContext jndiEnc = new InitialContext( ); PhoneHomeLocal phoneHome = (PhoneHomeLocal) jndiEnc.lookup("java:comp/env/ejb/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 using the accessor methods.
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 methods public 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 fields public abstract Collection getPhoneNumbers( ); public abstract void setPhoneNumbers(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 results in a
java.util.IllegalStateException
exception.
If no beans have been added to the phoneNumbers
relationship field, the getPhoneNumbers( )
method
returns an empty Collection
object. 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 without exposing those
mechanisms to the bean developer.[19]
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 fields public abstract Collection getPhoneNumbers( ); public abstract void setPhoneNumbers(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 that Customer A wants to transfer all of its phone numbers to
Customer B. It can accomplish this 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 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.
Once 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 is
Many
. These keywords establish 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.
Exercise 7.1 in the Workbook shows how to deploy this example on the
JBoss server.
By now, I imagine that you’re bored by all these phone numbers, credit cards, and addresses. 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.
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 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 reason relationships are set in ejbPostCreate( )
and not ejbCreate( )
is 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 Cruises 1, 2, and 3, and pass it to Cruise 4, Cruises 1 through 4 will all 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 sound like they’re different, but they’re not. 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 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 uniqueness 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 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,
not the ejbCreate( )
method. As stated previously,
the ejbCreate( )
method is not allowed to update
relationship fields; that is the job of ejbPostCreate( )
.
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. 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.[20]
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 with the Customer and Phone EJBs, this effect is usually undesirable and should be avoided; 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.[21] 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.
Moving an individual Reservation EJB from one Cruise to another is
similar to moving an individual bean in a one-to-many relationship:
the result is shown in Figure 7-9, when a Phone was
moved from one Customer to another. The net effect of using
Collection.addAll( )
in this situation 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 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 results 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 this 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. To establish a many-to-many,
bidirectional relationship, we create the
RESERVATION_CUSTOMER_LINK
table. 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 table in a normalized relational database.
To model the many-to-many, bidirectional relationship between the Customer and Reservation EJBs, we need to include collection-based relationship fields in both bean classes:
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. 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. The
Customer EJB now includes a reservations
relationship field:
public abstract class CustomerBean implements javax.ejb.EntityBean { ... // relationship fields public 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
bidirectional, the EJB container automatically adds the Reservation
EJB to the reservations
relationship field of the
Customer EJB. The following code illustrates this:
Collection customers = ... get local Customer EJBs CruiseLocal cruise = ... get a local Cruise EJB ReservationHomeLocal resHome = ... get local Reservation home ReservationLocal 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 changes 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. For example, a family might make 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 the CABIN
table maintains a foreign key to
the SHIP
table. While this relationship is
important, we don’t discuss it because we covered
the one-to-many, bidirectional relationship in this chapter. To
accommodate the many-to-many, unidirectional relationship between the
RESERVATION
and CABIN
table, we
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 in a many-to-many,
bidirectional relationship. Use of the Collection.addAll( )
operation to share entire collections has the same net
effect; 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, it doesn’t affect other Reservation EJBs that reference the Cabin bean (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>
Exercise 7.2 in the Workbook shows how to deploy this example on the JBoss server.
If two entity beans are to have a relationship, they must be deployed by the same deployment descriptor. 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 relationship fields to 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.
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 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 affect 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 for
the bean that is removed is set to null
. With
collection-based relationship fields, the entity that is deleted is
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 also want the Address EJBs referenced in
its billingAddress
and
homeAddress
relationship field to be deleted, in
order to avoid leaving 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.
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 in order 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 is
not be removed when the CUSTOMER
record is
deleted. This can result in a disconnected entity: rows in the
database that are 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. However, it’s important to
use a cascading delete with care. 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 may be 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 causes 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.
Exercise 7.3 in the Workbook shows how to deploy the examples in this section.
[19] 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.
[20] 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.
[21] The addAll( )
method must be supported by collection-based relationship
fields.