Optimistic Locking pattern for EJBs

Deploy transactionally safe EJB code

1 2 Page 2
Page 2 of 2

Having chosen the version counter mechanism to implement our optimistic locking solution, we must revise the design of the Customer entity bean. Additionally, the Customer EJB design is refactored to use the Business Interface pattern (see Resources) to make the solution clearer (see Figure 3).

Figure 3. Business Interface pattern

The EJB specification forces the bean class to provide an implementation for all methods declared in the remote interface. The specification also recommends against having the bean class directly implement the remote interface using the implements Java keyword. The EJB compiler finds any inconsistencies between the remote interface and the bean implementation.

Many errors uncovered by EJB compilation involve methods declared in the remote interface that aren't declared correctly in the bean class. The compiler could catch this in the first compilation step if the bean class could implement the remote interface directly. The Java compiler could then use the standard rules for enforcing the implementation of an interface.

The Business Interface pattern solves this problem. Both the remote interface and the bean class implement the business interface. The business interface is defined just like the bean's normal remote interface; the only difference is that it does not extend javax.ejb.EJBObject. The business interface declares the methods that can be called on the business object, but doesn't make any assumptions about the underlying implementation.

A client then interacts with the business interface, not the remote interface. This means that you can work with a remote bean, or a serializable "state" object, or with something in between. It doesn't matter to the client as long as the interface remains the same. As shown in Figure 3, we separate out the business implementation code from the EJB's technical contract. Note the following points about the Business Interface pattern as applied to this example:

  • The customer interface Business Interface defines all required business methods. No entity bean lifecycle methods reside here.
  • The Versionable interface defines our public methods for the optimistic locking solution. It is a separate interface because we can reuse it on any entity bean requiring this functionality. Not all entity beans participate in concurrent access; for example, the reservation entity bean is created only once and has no concurrency issues.
  • The CustomerObject (see the code below) class implements the Customer and Version interfaces. Until now, we have not mentioned EJBs, so in effect you could use this class on its own. This implementation bean implements the business interface, and then is extended by the actual EJB.
  • At this point, you should create the EJB-specific implementation that implements the EJB's technical contract.
  • Finally, the factory methods in the CustomerHome interface return a CustomerRemote that extends the Customer interface.

Here's the code:

CustomerObject

package com.valtech.bootcamp.carRental.business.customer;
import com.valtech.bootcamp.carRental.business.customer.Customer;
import com.valtech.bootcamp.carRental.business.customer.CustomerStateHolder;
import com.valtech.bootcamp.carRental.exceptions.ModifiedException;
import com.valtech.bootcamp.carRental.business.shared.*;
/**
 * This object implements all the business methods defined
 * in the contract with the Customer interface and also
 * provides implementations of the methods defined in the
 * Versionable interface.
 */
public class CustomerObject implements Customer, Versionable
{
   /**
    * the customer address
    */
   public String address;
   /**
    * the customer name
    */
   public String name;
   /**
    * the version of the customer, i.e the version number in 
    * the customer table
    */
   public int version;
   /**
    * the card number of the customer
    */
   public String cardNo;
   /**
    *CustomerObjectconstructor comment.
     */
   public CustomerObject()
   {
      super();
   }
   /**
    * returns a CustomerStateHolder containing all Customer attributes
    */
   public CustomerStateHolder getCustomerStateHolder()
   {
      System.err.println(version);
      CustomerStateHolder csh = new CustomerStateHolder(name, address, cardNo ,version);
      return csh;
   }
   /**
    * returns the name of the customer
    */
   public String getName()
   {
      return name;
   }
   /**
    * sets the name
    */
   public void setName(String name)
   {
       this.name = name;
   }
   /**
    * returns the version number of the customer
    */
   public int getVersion()
   {
       return version;
   }
   /**
    * sets the version number of the customer
    */
   public void setVersion(int newVersionNumber)
   {
       version = newVersionNumber;
   }
   /**
    * Setter for the address
    */
   public void setAddress(String address)
   {
      this.address = address;
   }
   /**
    * Setter for the card number
    */
   public void setCardNo(String cardNo)
   {
      this.cardNo = cardNo;
   }
   /**
    * This method does the work.
    * First it compares the version number of the value
    * the object is currently working with and the version number that the
    * entity bean is working with.
    * If these values differ, we are in the situation where we are
    * attempting to update data that has changed since we initially read the
    * data.
    * If the values are the same, we are working with data that has not
    * been updated since we started working with it. In this situation the
    * value of the version number is incremented and the entity bean
    * is updated.
    *
    * @param customerStateHolder The value object that updates
    * the details of the customer
    */
   public void setCustomerStateHolder(CustomerStateHolder customerStateHolder)
   {
      /**
       * The comparison is performed here. If the version numbers differ,
       * the Modified Exception is thrown.
       */
      if(customerStateHolder.getVersion()!= this.getVersion())
      {
         throw new ModifiedException("Data has been modified before your change has been commited");
      }
      /**
       * Otherwise we can proceed to extract the data from the value object
       * and update the version number.
       */
      else
      {
         setName(customerStateHolder.getName());
          setAddress(customerStateHolder.getAddress());
          setCardNo(customerStateHolder.getCardNo());
         int currentversion=this.getVersion();
         int newvnum =  currentversion  + 1;
         setVersion(newvnum);
         /**
          * Reset the version number to the new version number
          */
         customerStateHolder.setVersion(newvnum);
      }
    }
}

The sequence diagram in Figure 4 shows the customer-update use case. The bean is updated with the latest screen information using the setter methods in the CustomerStateHolder bean. Finally, the customer update is initiated by calling the setCustomer() method, which in turn delegates to the setCustomerStateHolder() method in the Customer entity EJB.

Figure 4. Set a customer

The code first gets the version attribute from the database and compares it to that passed in the CustomerStateHolder for update. If the comparison proves unsuccessful, an exception is thrown and no update is performed. Upon a successful comparison, the version count is incremented and this, along with the other Customer attributes, is updated with the latest information. Note that the reading and updating of the version field occurs within a transaction, thus ensuring an exclusive lock on the customer object; otherwise, some other user could update the version in between the version number's read and write to the database. This is done by demarking the setCustomerStateHolder() method with the required transaction attribute, as seen in EJB-Jar file.

In this way the entire customer update process proceeds within a transaction. The required attribute ensures that if a transaction were not started before the setCustomerStateHolder() is called, a new transaction would be initiated.

Get optimistic!

In this article, you've seen how the Optimistic Locking pattern -- complemented by the Business Interface pattern to decouple the business logic from the technical contract -- can produce a concurrent, scalable solution. Most importantly this portable solution does not depend on any particular application server implementation. You can easily adapt our solution to use other optimistic locking strategies such as timestamps or state comparisons. So get to it!

The code

You can download the complete source code for this article from Resources. It was developed in an extreme programming environment using iterative, use-case driven and risk-driven development techniques, unit testing, and pair programming. All code was developed on a Windows platform using CVS (on a Linux platform) as a software configuration management tool. The project used TortoiseCVS, an excellent CVS client that integrates directly into Windows Explorer.

You can find the code in the src directory, which includes two packages: the com package (for the source code) and a corresponding test package. We test with the JUnit testing framework. You can build the whole project with the Ant 1.2 Java-based build tool; the build file is also located in the src directory. The README file in the parent directory details the environment needed to compile the source code. The result is an .ear file you can drop into JBoss's deploy directory.

The code has also been released as an open source project and can be, found at http://jaba.sourceforge.net/, where you will find any code updates and a How-To on setting up Cocoon with JBoss/Tomcat (needed to run the examples).

Yasmin Akbar-Husain has been working as a senior consultant at Valtech since January 1999. She specializes in enterprise-scale distributed object systems, focusing on CORBA and J2EE architectures. Before coming to Valtech, she worked at large financial institutions where she defined software architectures and built and implemented frameworks and applications using Java, Smalltalk, and CORBA. She earned a B.S. in computer science in 1977 from the University of Essex and also holds a master's degree in computing. Eoin Lane has been working as a senior consultant and trainer at Valtech since early 2000. Prior to Valtech, in the mid-1990s he founded the intranet document company InConn Technologies, which provides intranet document management systems. With a PhD in physics and a background as a postdoctoral researcher and 10 years' experience in the IT industry, over the past three years Eoin has focused on Linux, object-oriented design, Java, J2EE, EJBs, and XML, with a particular interest in open source technologies.

Learn more about this topic

1 2 Page 2
Page 2 of 2