Chapter 7. CMP: Entity Relationships

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).

The Seven Relationship Types

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:

One-to-one, unidirectional

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.

One-to-one, bidirectional

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.

One-to-many, unidirectional

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.

One-to-many, bidirectional

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.)

Many-to-one, unidirectional

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.

Many-to-many, unidirectional

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.)

Many-to-many, bidirectional

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.

Abstract Persistence Schema

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 void setHomeAddress(AddressLocal address);
public abstract AddressLocal getHomeAddress( );

// 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.

Database Modeling

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.

One-to-One, Unidirectional Relationship

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.

One-to-one, unidirectional relationship

Figure 7-1. One-to-one, unidirectional relationship

Relational database schema

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.

One-to-one, unidirectional relationship in RDBMS

Figure 7-2. One-to-one, unidirectional relationship in RDBMS

Abstract programming model

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.

Exchanging references in a one-to-one, unidirectional relationship

Figure 7-3. Exchanging references in a one-to-one, unidirectional relationship

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.

Abstract persistence schema

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>

One-to-One, Bidirectional Relationship

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.

Relational database schema

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.

One-to-one, bidirectional relationship in RDBMS

Figure 7-4. One-to-one, bidirectional relationship in RDBMS

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.

Abstract programming model

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.

Exchanging references in a one-to-one, bidirectional relationship

Figure 7-5. Exchanging references in a one-to-one, bidirectional relationship

Abstract persistence schema

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.

One-to-Many, Unidirectional Relationship

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.

Relational database schema

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.

One-to-many, unidirectional relationship in RDBMS using reverse pointers

Figure 7-6. One-to-many, unidirectional relationship in RDBMS using reverse pointers

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.

Abstract programming model

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.

Removing a bean reference from a relationship-field collection

Figure 7-7. Removing a bean reference from a relationship-field collection

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.

Exchanging a relationship collection in a one-to-many, unidirectional relationship

Figure 7-8. Exchanging a relationship collection in a one-to-many, unidirectional relationship

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.

Exchanging a bean in a one-to-many, unidirectional relationship

Figure 7-9. Exchanging a bean in a one-to-many, unidirectional relationship

Once again, it’s the multiplicity of the relationship that prevents Phone 1 from referencing both Customer A and Customer B.

Abstract persistence schema

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.

The Cruise, Ship, and Reservation EJBs

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.

Cruise, Ship, Reservation, Cabin, and Customer class diagram

Figure 7-10. Cruise, Ship, Reservation, Cabin, and Customer class diagram

Many-to-One, Unidirectional Relationship

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.

Relational database schema

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.

Many-to-one, unidirectional relationship in RDBMS

Figure 7-11. Many-to-one, unidirectional relationship in RDBMS

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
)

Abstract programming model

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.

Sharing a bean reference in a many-to-one, unidirectional relationship

Figure 7-12. Sharing a bean reference in a many-to-one, unidirectional relationship

Abstract persistence schema

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, Bidirectional 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.

Relational database schema

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.

One-to-many/many-to-one, bidirectional relationship in RDBMS

Figure 7-13. One-to-many/many-to-one, bidirectional relationship in RDBMS

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.

Abstract programming model

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).

Sharing an entire collection in a one-to-many, bidirectional relationship

Figure 7-14. Sharing an entire collection in a one-to-many, bidirectional relationship

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.

Using Collection.addAll( ) in a one-to-many, bidirectional relationship

Figure 7-15. Using Collection.addAll( ) in a one-to-many, bidirectional relationship

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 .

Abstract persistence schema

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 Relationship

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.

Relational database schema

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 relationship in RDBMS

Figure 7-16. Many-to-many, bidirectional relationship in RDBMS

Many-to-many, bidirectional relationships always require a link table in a normalized relational database.

Abstract programming model

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.

Using Collection.addAll( ) in a many-to-many, bidirectional relationship

Figure 7-17. Using Collection.addAll( ) in a many-to-many, bidirectional relationship

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.

Sharing an entire collection in a many-to-many, bidirectional relationship

Figure 7-18. Sharing an entire collection 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.

Abstract persistence schema

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 Relationship

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.

Relational database schema

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.

Many-to-many, unidirectional relationship in RDBMS

Figure 7-19. Many-to-many, unidirectional relationship in RDBMS

Abstract programming model

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).

Removing beans in a many-to-many, unidirectional relationship

Figure 7-20. Removing beans in a many-to-many, unidirectional relationship

Abstract persistence schema

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.

Co-Location and the Deployment Descriptor

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.

Cascade Delete and Remove

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).

Titan Cruises class diagram

Figure 7-21. Titan Cruises class diagram

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.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.216.77.153