Relationship Fields

Entity beans can form relationships with other entity beans. In Figure 6-1, at the beginning of this chapter, the Customer EJB has a one-to-one relationship with the Address EJB. The Address EJB is a fine-grained business object that should always be accessed in the context of another entity bean, which means it should have only local interfaces and not remote interfaces. An entity bean can have relationships with many different entity beans at the same time. For example, we could easily add relationship fields for Phone, CreditCard, and other entity beans to the Customer EJB. At this point, we’re choosing to keep the Customer EJB simple.

Using Figure 6-1 as a guide, we define the Address EJB as follows:

package com.titan.address;

import javax.ejb.EntityContext;

public abstract class AddressBean implements javax.ejb.EntityBean {
    
    public Integer ejbCreateAddress(String street, String city, 
        String state, String zip)
    {
         setStreet(street);
         setCity(city);
         setState(state);
         setZip(zip);
         return null;
    }
    public void ejbPostCreateAddress(String street, String city,
        String state, String zip) {
    }

    // persistence fields

    public abstract Integer getId( );
    public abstract void setId(Integer id);
    public abstract String getStreet( );
    public abstract void setStreet(String street);
    public abstract String getCity( );
    public abstract void setCity(String city);
    public abstract String getState( );
    public abstract void setState(String state);
    public abstract String getZip( );
    public abstract void setZip(String zip);
    
    // standard callback methods
    
    public void setEntityContext(EntityContext ec){}
    public void unsetEntityContext( ){}
    public void ejbLoad( ){}
    public void ejbStore( ){}
    public void ejbActivate( ){}
    public void ejbPassivate( ){}
    public void ejbRemove( ){}
}

The AddressBean class defines several persistence fields (street, city, state, and zip) and an ejbCreateAddress( ) method, which is called when a new Address EJB is created. The persistence fields are represented by abstract accessor methods. These abstract methods are matched with XML deployment descriptor elements. At deployment time, the container maps the Customer and Address EJB’s persistence fields to the database. This means there must be a table in our relational database that contains columns matching the persistence fields in the Address EJB. In this example, we will use a separate ADDRESS table for storing address information:

CREATE TABLE ADDRESS 
(
    ID INT PRIMARY KEY NOT NULL, 
    STREET CHAR(40), 
    CITY CHAR(20),
    STATE CHAR(2),
    ZIP CHAR(10)
)

The ID column in this table is an auto-increment field, created automatically by the database or container system. It is the primary key of the Address EJB. Once the bean is created, its primary key must never again be modified. When primary keys are autogenerated values, such as the ID column in the ADDRESS table, the EJB container obtains the primary key value from the database.

The other columns in this table correspond to the Address bean’s persistence fields. Entity beans do not have to define all the columns in the corresponding table as persistence fields. In fact, there’s no requirement that an entity bean correspond to a single table; it may be persisted to columns in several different tables. The bottom line is that the container allows the abstract persistence schema of an entity bean to be mapped to a database in a variety of ways, allowing a clean separation between the persistence classes and the database.

In addition to the bean class, we must define the local interface for the Address EJB. This interface allows the EJB to be accessed by other entity beans (namely, the Customer EJB) within the same address space or process:

// Address EJB's local interface
public interface AddressLocal extends javax.ejb.EJBLocalObject {
    public String getStreet( );
    public void setStreet(String street);
    public String getCity( );
    public void setCity(String city);
    public String getState( );
    public void setState(String state);
    public String getZip( );
    public void setZip(String zip);
}

// Address EJB's local home interface
public interface AddressHomeLocal extends javax.ejb.EJBLocalHome {
    public AddressLocal createAddress(String street,String city,
        String state,String zip) throws javax.ejb.CreateException;
    public AddressLocal findByPrimaryKey(Integer primaryKey)
        throws javax.ejb.FinderException;
}

You may have noticed that the ejbCreate( ) method of the AddressBean class and the findByPrimaryKey( ) method of the home interface both define the primary key type as java.lang.Integer. The primary key is generated automatically. Most EJB vendors allow entity beans’ primary keys to be mapped to autogenerated fields. If your vendor does not support autogenerated primary keys, you must set the primary key’s value in the ejbCreate( ) method.

The relationship field for the Address EJB is defined in the CustomerBean class using an abstract accessor method, the same way that persistence fields are declared. In the following code, the CustomerBean has been modified to include the Address EJB as a relationship field:

import javax.ejb.EntityContext; 
import javax.ejb.CreateException;

public abstract class CustomerBean implements javax.ejb.EntityBean {  
    ...

    // persistence relationship
    public abstract AddressLocal getHomeAddress( );
               public abstract void setHomeAddress(AddressLocal address);
     
    // persistence fields
    public abstract boolean getHasGoodCredit( );
    public abstract void setHasGoodCredit(boolean creditRating);
    ...

The getHomeAddress( ) and setHomeAddress( ) accessor methods are self-explanatory; they allow the bean to access and modify its homeAddress relationship. The name of the accessor method is determined by the name of the relationship field, as declared in the deployment descriptor. In this case, we have named the customer’s address homeAddress, so the corresponding accessor method names will be getHomeAddress( ) and setHomeAddress( ).

To accommodate the relationship between the Customer EJB and the home address, a foreign key, ADDRESS_ID, is needed in the CUSTOMER table. The foreign key points to the ADDRESS record. In practice, it would be more common to give the ADDRESS table a foreign key to the CUSTOMER table. However, the schema used here demonstrates alternative database mappings:

CREATE TABLE CUSTOMER 
(
    ID INT PRIMARY KEY NOT NULL, 
    LAST_NAME CHAR(20), 
    FIRST_NAME CHAR(20),
    ADDRESS_ID INT
)

When a new Address EJB is created and set as the Customer EJB’s homeAddress relationship, the Address EJB’s primary key is placed in the ADDRESS_ID column of the CUSTOMER table:

// get local reference
AddressLocal address = ... 

// establish the relationship
customer.setHomeAddress(address);

To give the Customer a home address, we need to deliver the address information to the Customer. This appears to be a simple matter of declaring matching setHomeAddress( )/getHomeAddress( ) accessors in the remote interface, but it’s not! While it’s valid to make persistence fields available to remote clients, persistence relationships are more complicated. The remote interface of a bean is not allowed to expose its relationship fields. In the case of the homeAddress field, we have declared the type to be AddressLocal, which is a local interface, so the setHomeAddress( )/getHomeAddress( ) accessors cannot be declared in the remote interface of the Customer EJB. The reason for this restriction on remote interfaces is fairly simple: the EJBLocalObject, which implements the local interface, is optimized for use within the same address space or process as the bean instance and is not capable of being used across the network. In other words, references that implement the local interface of a bean cannot be passed across the network, so a local interface cannot be declared as a return type of a parameter of a remote interface.

Local interfaces (interfaces that extend javax.ejb.EJBLocalObject ), on the other hand, can expose any kind of relationship field. With local interfaces, the caller and the enterprise bean being called are located in the same address space, so they can pass around local references without a problem. For example, if we had defined a local interface for the Customer EJB, it could include a method that allows local clients to access its Address relationship directly:

public interface CustomerLocal extends javax.ejb.EJBLocalObject {
    public AddressLocal getHomeAddress( );
    public void setHomeAddress(AddressLocal address);
}

When it comes to the Address EJB, it’s better to define a local interface only because it’s such a fine-grained bean. To get around remote-interface restrictions, the business methods in the bean class exchange address data instead of Address references. For example, we can declare a method in the Customer bean that allows the client to send address information:

public abstract class CustomerBean implements javax.ejb.EntityBean {
       
    public Integer ejbCreate(Integer id) {
        setId(id);
        return null;
    }
    public void ejbPostCreate(Integer id) {
    }
    // business method
    public void setAddress(String street,String city,String state,String zip) {
        try {

            AddressLocal addr = this.getHomeAddress( );
            if(addr == null) {
                // Customer doesn't have an address yet. Create a new one.
                InitialContext cntx = new InitialContext( );
                AddressHomeLocal addrHome = (AddressHomeLocal)
                     cntx.lookup("java:comp/env/ejb/AddressHomeLocal");
                addr = addrHome.createAddress(street,city,state,zip);
                this.setHomeAddress(addr);
            } else {
                // Customer already has an address. Change its fields.
                addr.setStreet(street);
                addr.setCity(city);
                addr.setState(state);
                addr.setZip(zip);
            }

        } catch(Exception e) {
            throw new EJBException(e);
        }
    }
 
    ...

The setAddress( ) business method in the CustomerBean class is also declared in the remote interface of the Customer EJB, so it can be called by remote clients:

public interface CustomerRemote extends javax.ejb.EJBObject {
    
    public void setAddress(String street,String city,String state,String zip)
    throws RemoteException;

    public Name getName( ) throws RemoteException;
    public void setName(Name name) throws RemoteException;
    
    public boolean getHasGoodCredit( ) throws RemoteException;
    public void setHasGoodCredit(boolean creditRating) throws RemoteException;
    
}

When the CustomerRemote.setAddress( ) method is invoked on the CustomerBean, the method’s arguments are used to create a new Address EJB and set it as the homeAddress relationship field, if one doesn’t already exist. If the Customer EJB already has a homeAddress relationship, that Address EJB is modified to reflect the new address information.

When creating a new Address EJB, the home object is obtained from the JNDI ENC (environment naming context) and its createAddress( ) method is called. This results in the creation of a new Address EJB and the insertion of a corresponding ADDRESS record into the database. After the Address EJB is created, it’s used in the setHomeAddress( ) method. The CustomerBean class must explicitly call the setHomeAddress( ) method, or the new address will not be assigned to the customer. Creating an Address EJB without assigning it to the customer results in a disconnected Address EJB. More precisely, it results in an ADDRESS record in the database that is not referenced by any CUSTOMER records. Disconnected entity beans are fairly normal and even desirable in many cases. In this case, however, we want the new Address EJB to be assigned to the homeAddress relationship field of the Customer EJB.

Tip

The viability of disconnected entities depends, in part, on the referential integrity of the database. For example, if the referential integrity allows only non-null values for the foreign key column, creating a disconnected entity may result in a database error.

When the setHomeAddress( ) method is invoked, the container links the ADDRESS record to the CUSTOMER record automatically. In this case, it places the ADDRESS primary key in the CUSTOMER record’s ADDRESS_ID field and creates a reference from the CUSTOMER record to the ADDRESS record.

If the Customer EJB already has a homeAddress, we want to change its values instead of creating a new Address EJB. We don’t need to use setHomeAddress( ) if we are simply updating the values of an existing Address EJB, because the Address EJB we modified already has a relationship with the entity bean.

We also want to provide clients with a business method for obtaining a Customer EJB’s home address information. Since we are prohibited from sending an instance of the Address EJB directly to the client (because it’s a local interface), we must package the address data in some other form and send that to the client. There are two solutions to this problem: acquire the remote interface of the Address EJB and return that; or return the data as a dependent value object.

We can obtain the remote interface for the Address EJB only if one is defined. The Address EJB is too fine-grained to justify creating a remote interface, but in many other circumstances, a bean may indeed want to have a remote interface. If, for example, the Customer EJB referenced a SalesPerson EJB, the CustomerBean could convert the local reference into a remote reference. This would be done by accessing the local EJB object, getting its primary key (EJBLocalObject.getPrimaryKey( )), obtaining the SalesPerson EJB’s remote home from the JNDI ENC, and then using the primary key and remote home reference to find a remote interface reference:

public SalesRemote getSalesRep( ){
    SalesLocal local = getSalesPerson( );
    Integer primKey = local.getPrimaryKey( );
    InitialContext cntx = new InitialContext( );
    Object ref = cntx.lookup("java:comp/env/ejb/SalesHomeRemote");
    SalesHomeRemote home = (SalesHomeRemote)
        PortableRemoteObject.narrow(ref, SalesHomeRemote.class);

    SalesRemote remote = home.findByPrimaryKey( primKey );
    return remote;
}

The other option is to use a dependent value to pass the Address EJB’s data between remote clients and the Customer EJB. This is the approach recommended for fine-grained beans like the Address EJB—we don’t want to expose these beans directly to remote clients. The following code shows how the AddressDO dependent value class is used in conjunction with the local component interfaces of the Address EJB (the DO in AddressDO is a convention used in this book—it’s a qualifier that stands for “dependent object”):

public abstract class CustomerBean implements javax.ejb.EntityBean {
       
    public Integer ejbCreate(Integer id) {
        setId(id);
        return null;
    }
    public void ejbPostCreate(Integer id) {
    }
    // business method
    public AddressDO getAddress( ) {
        AddressLocal addrLocal = getHomeAddress( );
        if(addrLocal == null) return null;
        String street = addrLocal.getStreet( );
        String city = addrLocal.getCity( );
        String state = addrLocal.getState( );
        String zip = addrLocal.getZip( );
        AddressDO addrValue = new AddressDO(street,city,state,zip);
        return addrValue;
    }
               public void setAddress(AddressDO addrValue) 
               throws EJBException {
        
        String street = addrValue.getStreet( );
        String city = addrValue.getCity( );
        String state = addrValue.getState( );
        String zip = addrValue.getZip( );

        AddressLocal addr = getHomeAddress( );

        try {

        if(addr == null) {
            // Customer doesn't have an address yet. Create a new one.
            InitialContext cntx = new InitialContext( );
            AddressHomeLocal addrHome = (AddressHomeLocal)
                  cntx.lookup("java:comp/env/ejb/AddressHomeLocal");
            addr = addrHome.createAddress(street, city, state, zip);
            this.setHomeAddress(addr);
        } else {
            // Customer already has an address. Change its fields.
            addr.setStreet(street);
            addr.setCity(city);
            addr.setState(state);
            addr.setZip(zip);
        }

        } catch(NamingException ne) {
            throw new EJBException(ne);
        } catch(CreateException ce) {
            throw new EJBException(ce);
        }
    }
    ...

Here is the definition for an AddressDO dependent value class, which is used by the enterprise bean to send address information to the client:

public class AddressDO implements java.io.Serializable {
    private String street;
    private String city;
    private String state;
    private String zip;
    
    public AddressDO(String street, String city, String state, String zip ) {
        this.street = street;
        this.city = city;
        this.state = state;
        this.zip = zip;
    }
    public String getStreet( ) {
        return street;
    }
    public String getCity( ) {
        return city;
    }
    public String getState( ) {
        return state;
    }
    public String getZip( ) {
        return zip;
    }
}

The AddressDO dependent value is immutable: it cannot be altered once it is created. As stated earlier, immutability helps to reinforce the fact that the dependent value class is a copy, not a remote reference. To use the AddressDO, we add accessor methods to the CustomerRemote interface:

public interface CustomerRemote extends javax.ejb.EJBObject {
    
    public void setAddress(AddressDO address) throws RemoteException;
               public AddressDO getAddress( ) throws RemoteException;

    public void setAddress(String street,String city,String state,String zip)
    throws RemoteException;

    public Name getName( ) throws RemoteException;
    public void setName(Name name) throws RemoteException;
    
    public boolean getHasGoodCredit( ) throws RemoteException;
    public void setHasGoodCredit(boolean creditRating) throws RemoteException;
    
}

You can now use a client application to test the Customer EJB’s relationship with the Address EJB. Here is the client code that creates a new Customer, gives it an address, and then changes the address:

import javax.naming.InitialContext;
import javax.rmi.PortableRemoteObject;
import javax.naming.Context;
import javax.naming.NamingException;
import java.util.Properties;

public class Client {
    public static void main(String [] args) throws Exception {
        // obtain CustomerHomeRemote
        Context jndiContext = getInitialContext( );
        Object obj=jndiContext.lookup("CustomerHomeRemote");
        CustomerHomeRemote home = (CustomerHomeRemote) 
            javax.rmi.PortableRemoteObject.narrow(obj, 
            CustomerHomeRemote.class);
            
        // create a Customer
        Integer primaryKey = new Integer(1);
        CustomerRemote customer = home.create(primaryKey);
             
        // create an address
        AddressDO address = new AddressDO("1010 Colorado", 
            "Austin", "TX", "78701");
        // set address                              
        customer.setAddress(address);

               address = customer.getAddress( );

        System.out.print(primaryKey+" = ");
        System.out.println(address.getStreet( ));
        System.out.println(address.getCity( )+","+
                           address.getState( )+" "+
                           address.getZip( ));
                        
        // create a new address
        address = new AddressDO("1600 Pennsylvania Avenue NW", 
            "DC", "WA", "20500");
                                  
        // change Customer's address
        customer.setAddress(address);

               address = customer.getAddress( );

        System.out.print(primaryKey+" = ");
        System.out.println(address.getStreet( ));
        System.out.println(address.getCity( )+","+
                           address.getState( )+" "+
                           address.getZip( ));
                
        // remove Customer
        customer.remove( );
    }   
    
    public static Context getInitialContext( ) 
        throws javax.naming.NamingException {
        Properties p = new Properties( );
        // ... Specify the JNDI properties specific to the vendor.
        //return new javax.naming.InitialContext(p);
        return null;
    }
}

The following listing shows the EJB 2.1 deployment descriptor for the Customer and Address EJBs. You don’t need to worry about the details of the deployment descriptor yet; it will be covered in depth in Chapter 7.

<?xml version="1.0" encoding="UTF-8" ?>
<ejb-jar 
     xmlns="http://java.sun.com/xml/ns/j2ee"
     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
     xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee
                         http://java.sun.com/xml/ns/j2ee/ejb-jar_2_1.xsd"
     version="2.1">

    <enterprise-beans>
        <entity>
            <ejb-name>CustomerEJB</ejb-name>
            <home>com.titan.customer.CustomerHomeRemote</home>
            <remote>com.titan.customer.CustomerRemote</remote>
            <ejb-class>com.titan.customer.CustomerBean</ejb-class>
            <persistence-type>Container</persistence-type>
            <prim-key-class>java.lang.Integer</prim-key-class>
            <reentrant>False</reentrant>
            <cmp-version>2.x</cmp-version>
            <abstract-schema-name>Customer</abstract-schema-name>
            <cmp-field><field-name>id</field-name></cmp-field>
            <cmp-field><field-name>lastName</field-name></cmp-field>
            <cmp-field><field-name>firstName</field-name></cmp-field>
            <primkey-field>id</primkey-field>
            <security-identity><use-caller-identity/></security-identity>
        </entity>
        <entity>
            <ejb-name>AddressEJB</ejb-name>
            <local-home>com.titan.address.AddressHomeLocal</local-home>
            <local>com.titan.address.AddressLocal</local>
            <ejb-class>com.titan.address.AddressBean</ejb-class>
            <persistence-type>Container</persistence-type>
            <prim-key-class>java.lang.Integer</prim-key-class>
            <reentrant>False</reentrant>
            <cmp-version>2.x</cmp-version>
            <abstract-schema-name>Address</abstract-schema-name>
            <cmp-field><field-name>id</field-name></cmp-field>
            <cmp-field><field-name>street</field-name></cmp-field>
            <cmp-field><field-name>city</field-name></cmp-field>
            <cmp-field><field-name>state</field-name></cmp-field>
            <cmp-field><field-name>zip</field-name></cmp-field>
            <primkey-field>id</primkey-field>
            <security-identity><use-caller-identity/></security-identity>
        </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>
    <assembly-descriptor>
        <security-role>
            <role-name>Employees</role-name>
        </security-role>
        <method-permission>
            <role-name>Employees</role-name>
            <method>
                <ejb-name>CustomerEJB</ejb-name>
                <method-name>*</method-name>
            </method>
            <method>
                <ejb-name>AddressEJB</ejb-name>
                <method-name>*</method-name>
            </method>
        </method-permission>
        <container-transaction>
            <method>
                <ejb-name>AddressEJB</ejb-name>
                <method-name>*</method-name>
            </method>
            <method>
                <ejb-name>CustomerEJB</ejb-name>
                <method-name>*</method-name>
            </method>
            <trans-attribute>Required</trans-attribute>
        </container-transaction>
    </assembly-descriptor>
</ejb-jar>

The EJB 2.0 deployment descriptor looks the same, except it uses a document declaration that points to a DTD instead referencing an XML Schema. Here’s the difference:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE ejb-jar PUBLIC "-//Sun Microsystems, Inc.//DTD Enterprise
JavaBeans 2.0//EN" "http://java.sun.com/dtd/ejb-jar_2_0.dtd">

<ejb-jar>
   ...
</ejb-jar>

Exercise 6.3 in the Workbook shows how to deploy this example on the JBoss server.

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

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