Chapter 27. Exercises for Chapter 9

Exercise 9.1: A BMP Entity Bean

In this exercise, you will build and examine a simple EJB that uses bean-managed persistence (BMP) to synchronize the state of the bean with a database. You will also build a client application to test this Ship BMP bean.

Start Up JBoss

If JBoss is already running, there is no reason to restart it.

Initialize the Database

As in the CMP examples, the state of the entity beans will be stored in the database that is embedded in JBoss. JBoss was able to create all tables for CMP beans, but it cannot do the same for BMP beans because the deployment descriptors don’t contain any persistence information (object-to-relational mapping, for example). The bean is in fact the only one that knows how to load, store, remove, and find data. The persistence mapping is not described in a configuration file, but embedded in the bean code instead.

One consequence is that the database environment for BMP must always be built explicitly. To make this task easier for the BMP Ship example, Ship’s home interface defines two helpful home methods.

Tip

Entity beans can define home methods that perform operations related to the EJB component’s semantics but that are not linked to any particular bean instance. As an analogy, consider the static methods of a class: their semantics are generally closely related to the class’s semantics, but they’re not associated with any particular class instance. Don’t worry if this is not totally clear: Chapter 10 of the EJB book, explains all about home methods.

Here’s a partial view of the Ship EJB’s home interface:

public interface ShipHomeRemote extends javax.ejb.EJBHome 
{
    ...
    public void makeDbTable ( ) throws RemoteException;
    public void deleteDbTable ( ) throws RemoteException;
}

It defines two home methods. The first creates the table needed by the Ship EJB in the JBoss-embedded database and the second drops it.

The implementation of the makeDbTable( ) home method is essentially a CREATE TABLE SQL statement:

public void ejbHomeMakeDbTable ( ) throws SQLException
{
   PreparedStatement ps = null;
   Connection con = null;
   try
   {
      con = this.getConnection ( );
      
      System.out.println("Creating table SHIP...");
      ps = con.prepareStatement ("CREATE TABLE SHIP ( " +
                                 "ID INT PRIMARY KEY, " +
                                 "NAME CHAR (30), " +
                                 "TONNAGE DECIMAL (8,2), " +
                                 "CAPACITY INT" +
                                 ")" );
      ps.execute ( );
      System.out.println("...done!");
   }
   finally
   {
      try { if (ps != null) ps.close ( ); } catch (Exception e) {}
      try { if (con != null) con.close ( ); } catch (Exception e) {}
   }
}

The deleteDbTable( ) home method differs only by the SQL statement it executes:

     ...
     System.out.println("Dropping table SHIP...");
     ps = con.prepareStatement ("DROP TABLE SHIP");
     ps.execute ( );
     System.out.println("...done!");
     ...

We explain how to call these methods in a subsequent section.

Examine the EJB Standard Files

The Ship EJB source code requires no modification to run in JBoss, so the standard EJB deployment descriptor is very simple.

ejb-jar.xml (part I)

...
 <enterprise-beans>
   <entity>
      <description>
            This bean represents a cruise ship.
      </description>
      <ejb-name>ShipEJB</ejb-name>
      <home>com.titan.ship.ShipHomeRemote</home>
      <remote>com.titan.ship.ShipRemote</remote>
      <ejb-class>com.titan.ship.ShipBean</ejb-class>
      <persistence-type>Bean</persistence-type>
      <prim-key-class>java.lang.Integer</prim-key-class>
      <reentrant>False</reentrant>
      <security-identity><use-caller-identity/></security-identity>
      <resource-ref>
           <description>DataSource for the Titan DB</description>
           <res-ref-name>jdbc/titanDB</res-ref-name>
           <res-type>javax.sql.DataSource</res-type>
           <res-auth>Container</res-auth>
      </resource-ref>
  </entity>
 </enterprise-beans>
 ...

This first part of the deployment descriptor essentially tells the container that the Ship bean:

  • Is named ShipEJB.

  • Has a persistence type set to Bean because it’s a BMP bean.

  • Declares a reference to a data source named jdbc/titanDB.

Because the bean directly manages the persistence logic, the deployment descriptor does not contain any persistence information. In contrast, this information would have been mandatory for a CMP EJB.

The second part of the deployment descriptor declares the transactional and security attributes of the Ship bean.

ejb-jar.xml (part II)

...
<assembly-descriptor>

   <security-role>
      <description>
         This role represents everyone who is allowed full 
         access to the Ship EJB.
      </description>
     <role-name>everyone</role-name>
   </security-role>

   <method-permission>
     <role-name>everyone</role-name>
     <method>
         <ejb-name>ShipEJB</ejb-name>
         <method-name>*</method-name>
     </method>
   </method-permission>

   <container-transaction>
     <method>
        <ejb-name>ShipEJB</ejb-name>
        <method-name>*</method-name>
     </method>
     <trans-attribute>Required</trans-attribute>
   </container-transaction>

 </assembly-descriptor> 

</ejb-jar>

All methods of the Ship bean require a transaction. If no transaction is active when a method invocation enters the container, a new one will be started.

Tip

In entity beans, transactions are always managed by the container, never directly by the bean. Thus, all work done on transactional resources, such as databases, will implicitly be part of the transactional context of the container.

Examine the JBoss-Specific Files

If you don’t include a jboss.xml -specific deployment descriptor with your bean, JBoss will take the following actions at deployment time. It will:

  • Bind the Ship bean in the public JNDI tree under /ShipEJB (which is the name given to the bean in its associated ejb-jar.xml deployment descriptor).

  • Link the jdbc/titanDB data source expected by the bean to java:/DefaultDS, which is a default data source that represents the embedded database.

Unless you require different settings, you don’t need to provide a jboss.xml file. While this shortcut is generally useful for quick prototyping, it will not satisfy more complex deployment situations. Furthermore, using a JBoss-specific deployment descriptor enables you to fine-tune a container for a particular situation.

If you take a look at the $JBOSS_HOME/server/default/conf/standardjboss.xml file, you will find all the default container settings that are predefined in JBoss (standard BMP, standard CMP, clustered BMP, and so on). In JBoss, there’s a one-to-one mapping between a bean and a container, and each container can be configured independently.

Tip

This mapping was a design decision made by the JBoss container developers and has not been dictated by the EJB specification: other application servers may use another mapping.

When you write a JBoss-specific deployment descriptor, you have three options:

  • Don’t specify any container configuration. JBoss will use the default configuration found in standardjboss.xml.

  • Create a brand new container configuration. The default settings are not used at all. JBoss will configure the container only as you specify in jboss.xml.

  • Modify an existing configuration. JBoss loads the default settings from the existing configuration found in standardjboss.xml and overrides them with the settings you specify in the jboss.xml deployment descriptor. This solution allows you to make minor modifications to the default container with minimal writing in your deployment descriptor.

The Ship bean uses the last option in order to test its behavior with different commit options. As outlined below, this new configuration defines only a single setting (<commit-option>). All others are inherited from the Standard BMP EntityBean configuration declared in the standardjboss.xml file. We’ll discuss commit options in a dedicated section at the end of this chapter.

jboss.xml

<?xml version="1.0"?>

<!DOCTYPE jboss PUBLIC
      "-//JBoss//DTD JBOSS 4.0//EN"
      "http://www.jboss.org/j2ee/dtd/jboss_4_0.dtd">

<jboss>
...
<container-configurations>
   <container-configuration>
      <container-name>Standard BMP EntityBean</container-name>
      <commit-option>A</commit-option>
   </container-configuration>
</container-configurations>
...

Because a single deployment descriptor may define multiple EJBs, the role of the <ejb-name> tag is to link the definitions from the ejb-jar.xml and jboss.xml files. You can consider this tag to be the bean’s identifier. The <jndi-name> tag determines the name under which the client applications will be able to look up the EJB’s home interface, in this case ShipHomeRemote.

You can also see how the bean refers to a specific configuration, thanks to the <configuration-name> tag.

...
   <enterprise-beans>
      <entity>
      <ejb-name>ShipEJB</ejb-name>
      <jndi-name>ShipHomeRemote</jndi-name>
      <configuration-name>Standard BMP EntityBean
      </configuration-name>
      <resource-ref>
         <res-ref-name>jdbc/titanDB</res-ref-name>
         <jndi-name>java:/DefaultDS</jndi-name>
      </resource-ref>
      </entity>
   </enterprise-beans>
</jboss>

The Ship bean BMP implementation needs to establish a database connection explicitly. It’s the getConnection( ) method that manages the acquisition of this resource.

ShipBean.java

private Connection getConnection ( ) throws SQLException
{
   try
   {
      Context jndiCntx = new InitialContext ( );
      DataSource ds =
      (DataSource)jndiCntx.lookup ("java:comp/env/jdbc/titanDB");         
      return ds.getConnection ( );
      ...

The bean expects to find a data source bound to the java:comp/env/jdbc/titanDB JNDI name. That’s why the ejb-jar.xml file contains the following declaration.

ejb-jar.xml

...
<resource-ref>
     <description>DataSource for the Titan DB</description>
     <res-ref-name>jdbc/titanDB</res-ref-name>
     <res-type>javax.sql.DataSource</res-type>
     <res-auth>Container</res-auth>
</resource-ref>
...

Then jboss.xml maps the jdbc/titanDB data source name to the actual name defined in JBoss.

jboss.xml

...
<resource-ref>
  <res-ref-name>jdbc/titanDB</res-ref-name>
  <jndi-name>java:/DefaultDS</jndi-name>
</resource-ref>
...

In any default JBoss installation, java:/DefaultDS represents the embedded database.

Build and Deploy the Example Programs

Perform the following steps:

  1. Open a command prompt or shell terminal and change to the ex9_1 directory created by the extraction process

  2. Set the JAVA_HOME and JBOSS_HOME environment variables to point to where your JDK and JBoss 4.0 are installed. Examples:

    Windows:C:workbookex9_1> set JAVA_HOME=C:jdk1.4.2 C:workbookex9_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
  3. Add ant to your execution path.

    Windows:C:workbookex9_1> set PATH=..antin;%PATH%
    Unix:$ export PATH=../ant/bin:$PATH
  4. 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.

Examine the Client Application

In the Section 27.1.2 section earlier in this chapter, you saw how the bean implements the home methods that create and drop the table in the database. Now you’ll see how the client application calls these home methods.

Client_91.java

public class Client_91
{
   public static void main (String [] args)
   {
      try
      {
         Context jndiContext = getInitialContext ( );
         
         Object ref = jndiContext.lookup ("ShipHomeRemote");
         ShipHomeRemote home = (ShipHomeRemote)
         PortableRemoteObject.narrow (ref,ShipHomeRemote.class);
         
         // We check if we have to build the database schema...
         //
         if ( (args.length > 0) && 
                     args[0].equalsIgnoreCase ("CreateDB") )
         {
            System.out.println ("Creating database table...");
            home.makeDbTable ( );
         }
         // ... or if we have to drop it...
         //
         else if ( (args.length > 0) && 
                     args[0].equalsIgnoreCase ("DropDB") )
         {
            System.out.println ("Dropping database table...");
            home.deleteDbTable ( );
         }
         else
         ...

Depending on the first argument found on the command line (CreateDB or DropDB), the client application calls the corresponding home method.

If nothing is specified on the command line, the client will test our BMP bean:

...
else
{
   // ... standard behavior
   //
   System.out.println ("Creating Ship 101..");
   ShipRemote ship1 = home.create (new Integer 
                      (101),"Edmund Fitzgerald");

   ship1.setTonnage (50000.0);
   ship1.setCapacity (300);

   Integer pk = new Integer (101);
            
   System.out.println ("Finding Ship 101 again..");
   ShipRemote ship2 = home.findByPrimaryKey (pk);

   System.out.println (ship2.getName ( ));
   System.out.println (ship2.getTonnage ( ));
   System.out.println (ship2.getCapacity ( ));

   System.out.println ("ship1.equals (ship2) == " +
   ship1.equals (ship2));

   System.out.println ("Removing Ship 101..");
   ship2.remove ( );
}
...

The client application first creates a new Ship and calls some of its remote methods to set its tonnage and capacity. Then it finds the bean again by calling findByPrimaryKey( ) and compares the bean references for equality. Because they represent the same bean instance, they must be equal. We’ve omitted the exception handling because it deserves no specific comments.

Run the Client Application

Testing the BMP bean is a three-step process that involves:

  1. Creating the database table

  2. Testing the bean (possibly many times)

  3. Dropping the database table

For each of these steps, a different Ant target is available.

Creating the database table

To create the table, use the createdb_91 Ant target:

C:workbookex9_1>ant createdb_91
Buildfile: build.xml

prepare:

compile:

createdb_91:
     [java] Creating database table...

On the JBoss side, the BMP bean displays the following lines:

...
12:31:42,584 INFO  [STDOUT] Creating table SHIP...
12:31:42,584 INFO  [STDOUT] ...done!
...

Once this step has been performed, the actual testing of the BMP bean can take place.

Tip

If you’re having trouble creating the database, shut down JBoss, then run the Ant build target clean.db. This removes all database files and allows you to start fresh.

Testing the BMP bean

To test the BMP bean, use the run.client_91 Ant target:

C:workbookex9_1>ant run.client_91
Buildfile: build.xml

prepare:

compile:

run.client_101:
     [java] Creating Ship 101..
     [java] Finding Ship 101 again..
     [java] Edmund Fitzgerald
     [java] 50000.0
     [java] 300
     [java] ship1.equals (ship2) == true
     [java] Removing Ship 101..

Analyzing the effects of transactions and commit options

Even though it’s not particularly related to BMP beans, let’s focus on an interesting problem that arises when the client first creates and initializes the bean:

ShipRemote ship1 = home.create (new Integer 
                   (101),"Edmund Fitzgerald");

ship1.setTonnage (50000.0);
ship1.setCapacity (300);

This piece of code generates three different transactions on the server side. The client does not implicitly start any transaction in its code. The transaction starts only when the invocation enters the bean container and commits when the invocation leaves the container. Thus, when the client performs three calls, each one is executed in its own transactional context.

Look at the implications for the BMP bean:

14:36:31,730 INFO  [STDOUT] ejbCreate( ) pk=101 name=Edmund Fitzgerald
14:36:31,780 INFO  [STDOUT] ejbStore( ) pk=101
14:36:31,840 INFO  [STDOUT] setTonnage( )
14:36:31,840 INFO  [STDOUT] ejbStore( ) pk=101
14:36:31,860 INFO  [STDOUT] setCapacity( )
14:36:31,860 INFO  [STDOUT] ejbStore( ) pk=101

As you can see, ejbStore( ) is called at the end of each transaction! Consequently, these three lines of code cause the bean to be stored three times. Worst of all, after any method invocation, the container has no way of knowing whether the state of the bean has been modified, and thus, to be on the safe side, it triggers storage of the bean. Given that there is no read-only method concept in EJBs, calls to get methods also trigger calls to ejbStore( ):

15:03:19,301 INFO  [STDOUT] getName( )
15:03:19,311 INFO  [STDOUT] ejbStore( ) pk=101
15:03:19,331 INFO  [STDOUT] getTonnage( )
15:03:19,331 INFO  [STDOUT] ejbStore( ) pk=101
15:03:19,371 INFO  [STDOUT] getCapacity( )
15:03:19,371 INFO  [STDOUT] ejbStore( ) pk=101

In the execution of the test program, ejbStore( ) is called seven times.

You can see that transaction boundaries (i.e., where transactions are started and stopped) directly influence the number of callbacks from the container to the Ship bean, and consequently have a direct effect on performance. We’ll now focus on another setting that also affects the set of callback methods the container will invoke on the bean: the commit option . The commit option determines how an entity bean container can make use of its cache. Remember from the container configuration section that the bean is currently using commit option A. Let’s examine all the options and their effects.

If you select commit option A, the entity bean container is allowed to cache any bean that it has loaded. Next time an invocation targets a bean that is already in the application server cache,[74] the container will not have to make a costly database access call to load it again.

If you select commit option B or C, the entity bean container is allowed to cache a bean only if it loads that bean during the lifetime of the currently running transaction. Once the transaction commits or rolls back, the container must remove the bean from the cache. The next time an invocation targets the bean, the container will have to reload it from the database.

That extra reloading is costly—but you must use B or C[75] whenever the data represented by the container can also be modified by other means. Direct database access calls through a console, for example, will cause the container cache to become unsychronized with the database, leading to incorrect computations and other dire results. A container must not use commit option A unless it “owns” the database (or, more accurately, the specific tables it accesses).

Most of the time, this “black or white” approach isn’t satisfactory: in real-world applications, commit option A can be used only very rarely, and commit options B and C will preclude useful cache optimizations. To circumvent these limitations, JBoss provides some proprietary optimizations: an additional commit option, distributed cache invalidations, and even a distributed transactional cache with various locking policies (JBossCache). See the JBoss web site for more information.

The JBoss-proprietary commit option D is a compromise between options A and C: The bean instance can be cached across transactions, but a configurable timeout value indicates when this cached data is stale and must be reloaded from the database. This option is very useful when you want some of the efficiency of commit option A, but want cached entities to be updated periodically to reflect modifications by an external system.

Tip

Remember that each EJB deployed in JBoss has its own container. Consequently, for each EJB, you can define the commit option that best fits its specific environment. For example, a Zip code entity bean (with data that will most probably never change) could use commit option A, whereas the Order EJB would use commit option C.

After this introduction to commit options, it becomes possible to guess that the container is currently using commit option A without looking at its configuration. Two pieces of evidence lead us to this conclusion:

  • The findByPrimaryKey( ) call isn’t displayed in the log. The container first checks whether the cache already contains an instance for the given primary key. Because it does, there is no need to invoke the bean implementation’s ejbFindByPrimaryKey( ) method.

  • ejbLoad( ) isn’t called for the bean. At the start of each new transaction, it’s already in cache and there is no need to reload it from the database.

Tip

Note that only direct access to a given bean (using its remote reference) or findByPrimaryKey( ) calls can be resolved in cache. All other queries (findAll( ), findByCapacity( ), and so on) must be resolved by the database directly (there is no way to perform queries in the container cache directly).

To see how different commit options lead to different behavior, change the commit option in jboss.xml from A to C:

jboss.xml

   ...
   <container-configurations>
      <container-configuration>
         <container-name>Standard BMP EntityBean</container-name>
         <commit-option>C</commit-option>
      </container-configuration>
   </container-configurations>
   ...

Run the tests again. You’ll see:

14:41:29,798 INFO  [STDOUT] ejbCreate( ) pk=101 name=Edmund Fitzgerald
14:41:30,449 INFO  [STDOUT] ejbStore( ) pk=101
14:41:30,539 INFO  [STDOUT] ejbLoad( ) pk=101
14:41:30,599 INFO  [STDOUT] setTonnage( )
14:41:30,609 INFO  [STDOUT] ejbStore( ) pk=101
14:41:30,659 INFO  [STDOUT] ejbLoad( ) pk=101
14:41:30,669 INFO  [STDOUT] setCapacity( )
14:41:30,679 INFO  [STDOUT] ejbStore( ) pk=101
14:41:30,709 INFO  [STDOUT] ejbFindByPrimaryKey( ) primaryKey=101
14:41:30,729 INFO  [STDOUT] ejbLoad( ) pk=101
14:41:30,750 INFO  [STDOUT] getName( )
14:41:30,750 INFO  [STDOUT] ejbStore( ) pk=101
14:41:30,780 INFO  [STDOUT] ejbLoad( ) pk=101
14:41:30,790 INFO  [STDOUT] getTonnage( )
14:41:30,800 INFO  [STDOUT] ejbStore( ) pk=101
14:41:30,840 INFO  [STDOUT] ejbLoad( ) pk=101
14:41:30,850 INFO  [STDOUT] getCapacity( )
14:41:30,860 INFO  [STDOUT] ejbStore( ) pk=101
14:41:30,880 INFO  [STDOUT] ejbLoad( ) pk=101
14:41:30,900 INFO  [STDOUT] ejbStore( ) pk=101
14:41:30,910 INFO  [STDOUT] ejbRemove( ) pk=101

Now, in addition to the ejbStore( ) calls you’ve already seen, you see calls to ejbLoad( ) at the start of each new transaction, and the call to ejbFindByPrimaryKey( ) as well, which reaches the bean implementation because it cannot be resolved within the cache.

Possible optimizations

As you have seen during the execution of the client application, the Ship bean performs many ejbLoad( ) and ejbStore( ) operations. There are two reasons behind this behavior:

  • Many transactions are started.

  • The Ship bean BMP code is not optimized.

You can reduce the number of transactions in several ways:

  • Define less fine-grained methods that return all attributes of the bean in a single data object.

  • Add a new create method with many parameters, so a single call can create and initialize the bean.

  • Use the Façade pattern: create a stateless session bean that starts a single transaction, then performs all the steps in that one transaction.

  • Start a transaction in the client application, using a UserTransaction object.

BMP code optimization is a wide topic. Here are some tricks that are frequently used:

  • Use an isModified flag in your bean. Set it to true each time the state of the bean changes (in set methods, for example). In the implementation of ejbStore( ), perform the actual database call only if isModified is true. Think about the impact on the test application. All the ejbStore( ) calls resulting from invocations to get methods will detect that no data has been modified and will not try to synchronize with the database.

  • Detect which fields are actually modified during a transaction and update only those particular fields in the database. This tactic is especially useful for beans with lots of fields or with fields that contain large amounts of data. Contrast with the Ship BMP bean as it’s currently written, where each setXXX( ) call updates all fields of the database even though only one actually changes.

Note that any decent CMP engine performs many of these optimizations by default.

Dropping the database table

Once you’ve run all the tests, clean the database environment associated with the BMP bean by removing the unused table. Use the dropdb_91 target:

C:workbookex9_1>ant dropdb_91
Buildfile: build.xml

prepare:

compile:

dropdb_101:
     [java] Dropping database table...

On the JBoss side, the BMP bean logs the following lines:

...
14:40:34,339 INFO  [STDOUT] Dropping table SHIP...
14:40:34,349 INFO  [STDOUT] ...done!
...


[74] We are speaking about the application server cache, not the database cache. While database caches are critical to performance, application server caches can improve it even further.

[75] The difference between commit option B and C is very small: when a transaction commits, a container using commit option C must effectively throw away the bean instance while a container using commit option B may keep it and reuse it later. This distinction allows commit option B to be used for very specific container optimizations (such as checking whether the data has really been modified in the database and reusing the instance if no modification has occurred, instead of reloading the whole state).

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

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