This exercise walks you through implementing a complex set of interrelated entity beans defined in Chapter 7 of the EJB book.
If JBoss is not running, start it up. If it’s already running, there’s no reason to restart it.
The database table for this exercise will automatically be created in JBoss’s default database, HypersonicSQL, when the EJB JAR is deployed.
Perform the following steps:
Open a command prompt or shell terminal and change to the
ex07_1
directory created by the extraction
process
Set the JAVA_HOME
and
JBOSS_HOME
environment variables to point to where
your JDK and JBoss 4.0 are installed. Examples:
Windows:C:workbookex07_1> set JAVA_HOME=C:jdk1.4.2 C:workbookex07_1> set JBOSS_HOME=C:jboss-4.0
|
Unix:$ export JAVA_HOME=/usr/local/jdk1.4.2 $ export JBOSS_HOME=/usr/local/jboss-4.0
|
Add ant
to your execution path.
Windows:C:workbookex07_1> set PATH=..antin;%PATH%
|
Unix:$ export PATH=../ant/bin:$PATH
|
Perform the build by typing ant
.
As in the last exercise, you will see titan.jar
rebuilt, copied to the JBoss deploy
directory,
and redeployed by the application server.
This chapter introduces no new features in JBoss-specific files. Please review Exercise 6.1 to understand the JBoss-specific files in this example. Also, this chapter implements nonperformance-tuned entity beans and relies on the CMP 2.0 engine to create all database tables. To learn about JBoss’s extensive configuration options, please review the advanced CMP 2.0 documentation at http://www.jboss.org.
From this chapter on, we no longer use remote entity bean interfaces (so the example code matches the code illustrated in the EJB section of this book). Accordingly, the Customer EJB switches to local-only interfaces:
CustomerHomeRemote
becomes
CustomerHomeLocal
.
CustomerRemote
becomes
CustomerLocal
.
Bean interface methods no longer throw
RemoteException
s.
The ejb-jar.xml
descriptor changes to use local
interfaces. Thus:
<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>
changes to:
<ejb-name>CustomerEJB</ejb-name> <local-home>com.titan.customer.CustomerHomeLocal</local-home> <local>com.titan.customer.CustomerLocal</local> <ejb-class>com.titan.customer.CustomerBean</ejb-class>
The JNDI binding in jboss.xml
changes as well.
Thus:
<entity> <ejb-name>CustomerEJB</ejb-name> <jndi-name>CustomerHomeRemote</jndi-name> </entity>
changes to:
<entity> <ejb-name>CustomerEJB</ejb-name> <local-jndi-name>CustomerHomeLocal</local-jndi-name> </entity>
Because interfaces are now local, the example programs no longer need to use dependent value classes to set up relationships like Customer-Address. This change simplifies the code and allows you to pass local entity beans such as Address, Credit Card, and Phone to Customer EJB methods directly.
Another consequence is that remote clients can no longer invoke
business logic on the entity beans implemented in this chapter.
Instead, you’ll implement all example business logic
in the methods of a stateless session bean. Also, EJB containers
don’t allow the manipulation of a relationship
collection (including iteration through the collection) outside the
context of a transaction. In JBoss, all bean methods are
Required
by default, so all example test code will
run within a transaction. Chapter 16 in the EJB
book discusses transactions in more detail.
To execute these examples from the command line, implement separate, distinct remote clients that get a reference to the stateless test bean and invoke the appropriate test method.
The Client_71a
example program reveals the
unidirectional relationship between Customer and Address. The
business logic for this example is implemented in
com.titan.test.Test71Bean
in the
test71a( )
method.
In test71a( )
, output is written to the
PrintWriter
created below. The method finishes by
extracting a String
from the
PrintWriter
and passing it back to the remote
client for display:
public String test71a( ) throws RemoteException { String output = null; StringWriter writer = new StringWriter( ); PrintWriter out = new PrintWriter(writer); try {
The first part of test71a( )
simply fetches the
home interfaces of Customer and Address from JNDI. It then creates
both a Customer and an Address:
InitialContext jndiContext = getInitialContext( ); Object obj = jndiContext.lookup("CustomerHomeLocal"); CustomerHomeLocal customerhome = (CustomerHomeLocal)obj; obj = jndiContext.lookup("AddressHomeLocal"); AddressHomeLocal addresshome = (AddressHomeLocal)obj; out.println("Creating Customer 71"); Integer primaryKey = new Integer(71); CustomerLocal customer = customerhome.create(primaryKey); customer.setName( new Name("Smith","John") ); AddressLocal addr = customer.getHomeAddress( ); if (addr==null) { out.println("Address reference is NULL, Creating one and setting in Customer.."); addr = addresshome.createAddress("333 North Washington" ,"Minneapolis" ,"MN","55401");
A call to customer.setHomeAddress( )
sets up the
relationship:
customer.setHomeAddress(addr); } ...
Next, modify the address directly with new information. Calling the Address object’s set methods is the correct way to modify a unidirectional relationship that has already been set up.
addr.setStreet("445 East Lake Street"); addr.setCity("Wayzata"); addr.setState("MN"); addr.setZip("55432"); ...
The next bit of code shows the wrong way to
modify a unidirectional relationship
that’s already been created. Instead of modifying
the existing Address entity, it creates a new one. Passing the new
one to customer.setHomeAddress( )
orphans the old
one, which thereafter just sits there in the database, unused and
forgotten. The result is a database
“leak:”
addr = addresshome.createAddress("700 Main Street" ,"St. Paul","MN","55302"); ... customer.setHomeAddress(addr);
Two different relationships can share the same entity. This code shares a single address between the Home Address and Billing Address relationships:
addr = customer.getHomeAddress( ); ... customer.setBillingAddress(addr); AddressLocal billAddr = customer.getBillingAddress( ); AddressLocal homeAddr = customer.getHomeAddress( );
The Billing Address and Home Address now refer to the same bean:
if (billAddr.isIdentical(homeAddr)) { out.println("Billing and Home are the same!"); } else { out.println("Billing and Home are NOT the same! BUG IN JBOSS!"); } } catch (Exception ex) { ex.printStackTrace(out); }
Finally, test71a( )
closes the PrintWriter,
extracts the output string, and returns it to the client for display:
out.close( ); output = writer.toString( ); return output; }
In order to run Client_71a,
invoke the Ant task
run.client_71a
. Remember to set your
JBOSS_HOME
and PATH
environment
variables. The output should look something like this:
C:workbookex07_1>ant run.client_71a Buildfile: build.xml prepare: compile: run.client_71a: [java] Creating Customer 71 [java] Address reference is NULL, Creating one and setting in Customer.. [java] Address Info: 333 North Washington Minneapolis, MN 55401 [java] Modifying Address through address reference [java] Address Info: 445 East Lake Street Wayzata, MN 55432 [java] Creating New Address and calling setHomeAddress [java] Address Info: 700 Main Street St. Paul, MN 55302 [java] Retrieving Address reference from Customer via getHomeAddress [java] Address Info: 700 Main Street St. Paul, MN 55302 [java] Setting Billing address to be the same as Home address. [java] Testing that Billing and Home Address are the same Entity. [java] Billing and Home are the same!
The Client_71b
program illustrates a simple one-to-one
bidirectional relationship between a Customer bean and a Credit Card
bean. The business logic for this example is implemented in
com.titan.test.Test71Bean
, in the
test71b( )
method. Examine the code for this
example.
You use the default JNDI context to obtain references to the local home interfaces of the Customer and Credit Card EJBs. The code also creates an instance of a Customer EJB:
// obtain CustomerHome InitialContext jndiContext = getInitialContext( ); Object obj = jndiContext.lookup("CustomerHomeLocal"); CustomerHomeLocal customerhome = (CustomerHomeLocal)obj; obj = jndiContext.lookup("CreditCardHomeLocal"); CreditCardHomeLocal cardhome = (CreditCardHomeLocal)obj; Integer primaryKey = new Integer(71); CustomerLocal customer = customerhome.create(primaryKey); customer.setName( new Name("Smith","John") );
Next, create an instance of a Credit Card. Notice that you don’t need to pass in a primary key; the crude algorithm introduced in Exercise 6.3 generates one automatically:
// set Credit Card info Calendar now = Calendar.getInstance( ); CreditCardLocal card = cardhome.create(now.getTime( ), "370000000000001", "John Smith", "O'Reilly");
Then you establish the one-to-one bidirectional relationship between
Customer and Credit Card simply by calling the Customer
EJB’s setCreditCard( )
method:
customer.setCreditCard(card);
The following code illustrates the bidirectional nature of the relationship by navigating from a Credit Card to a Customer and vice versa:
String cardname = customer.getCreditCard( ).getNameOnCard( ); out.println("customer.getCreditCard( ).getNameOnCard( )=" + cardname); Name name = card.getCustomer( ).getName( ); String custfullname = name.getFirstName( ) + " " + name.getLastName( ); out.println("card.getCustomer( ).getName( )="+custfullname);
Finally, the code illustrates how to destroy the relationship between the Customer and Credit Card beans:
card.setCustomer(null); CreditCardLocal newcardref = customer.getCreditCard( ); if (newcardref == null) { out.println ("Card is properly unlinked from customer bean"); } else { out.println("Whoops, customer still thinks it has a card! BUG IN JBOSS!"); }
In order to run Client_71b,
invoke the Ant task
run.client_71b
. Remember to set your
JBOSS_HOME
and PATH
environment
variables. The output should look something like this:
C:workbookex07_1>ant run.client_71b Buildfile: build.xml prepare: compile: run.client_71b: [java] Finding Customer 71 [java] Creating CreditCard [java] Linking CreditCard and Customer [java] Testing both directions on relationship [java] customer.getCreditCard( ).getNameOnCard( )=John Smith [java] card.getCustomer( ).getName( )=John Smith [java] Unlink the beans using CreditCard, test Customer side [java] Card is properly unlinked from customer bean [java]
The Client_71c
program illustrates the proper use of a
one-to-many unidirectional relationship between customers and their
phone numbers. The business logic for this example is implemented in
com.titan.test.Test71Bean
, in the
test71c( )
method.
First, the test code locates the Customer home interface through JNDI, then finds the Customer that needs new phone numbers:
// obtain CustomerHome InitialContext jndiContext = getInitialContext( ); Object obj = jndiContext.lookup("CustomerHomeLocal"); CustomerHomeLocal home = (CustomerHomeLocal)obj; // Find Customer 71 Integer primaryKey = new Integer(71); CustomerLocal customer = home.findByPrimaryKey(primaryKey);
The next bit of code invokes the Customer helper method
addPhoneNumber( )
to relate two phone numbers to
the customer and outputs the contents of the customer-phone
relationship after each addition:
// Display current phone numbers and types out.println("Starting contents of phone list:"); ArrayList vv = customer.getPhoneList( ); for (int jj=0; jj<vv.size( ); jj++) { String ss = (String)(vv.get(jj)); out.println(ss); } // add a new phone number out.println("Adding a new type 1 phone number.."); customer.addPhoneNumber("612-555-1212",(byte)1); out.println("New contents of phone list:"); vv = customer.getPhoneList( ); for (int jj=0; jj<vv.size( ); jj++) { String ss = (String)(vv.get(jj)); out.println(ss); } // add a new phone number out.println("Adding a new type 2 phone number.."); customer.addPhoneNumber("800-333-3333",(byte)2); out.println("New contents of phone list:"); vv = customer.getPhoneList( ); for (int jj=0; jj<vv.size( ); jj++) { String ss = (String)(vv.get(jj)); out.println(ss); }
This code uses the updatePhoneNumber( )
helper
method to modify an existing phone number:
// update a phone number out.println("Updating type 1 phone numbers.."); customer.updatePhoneNumber("763-555-1212",(byte)1); out.println("New contents of phone list:"); vv = customer.getPhoneList( ); for (int jj=0; jj<vv.size( ); jj++) { String ss = (String)(vv.get(jj)); out.println(ss); }
Finally, this code illustrates how to remove a member of a one-to-many unidirectional relationship:
// delete a phone number out.println("Removing type 1 phone numbers from this Customer.."); customer.removePhoneNumber((byte)1); out.println("Final contents of phone list:"); vv = customer.getPhoneList( ); for (int jj=0; jj<vv.size( ); jj++) { String ss = (String)(vv.get(jj)); out.println(ss); }
Note that the phone entity hasn’t been destroyed. It’s still in the database; it’s just no longer related to this customer bean.
In order to run Client_71c,
invoke the Ant task
run.client_71c
. Remember to set your
JBOSS_HOME
and PATH
environment
variables. The output should look something like this:
C:workbookex07_1>ant run.client_71c Buildfile: build.xml prepare: compile: run.client_71c: [java] Starting contents of phone list: [java] Adding a new type 1 phone number.. [java] New contents of phone list: [java] Type=1 Number=612-555-1212 [java] Adding a new type 2 phone number.. [java] New contents of phone list: [java] Type=1 Number=612-555-1212 [java] Type=2 Number=800-333-3333 [java] Updating type 1 phone numbers.. [java] New contents of phone list: [java] Type=1 Number=763-555-1212 [java] Type=2 Number=800-333-3333 [java] Removing type 1 phone numbers from this Customer.. [java] Final contents of phone list: [java] Type=2 Number=800-333-3333