ONJava.com -- The Independent Source for Enterprise Java
oreilly.comSafari Books Online.Conferences.

advertisement

AddThis Social Bookmark Button

EJB Inheritance, Part 2
Pages: 1, 2, 3

Vertical Mapping



Vertical table mapping is similar to horizontal table mapping, except columns are not repeated. Instead, inherited fields are stored as a row of the "base table," and other fields of the subclass are stored in a related row of the "subclass table." Here's a diagram that illustrates this statement, for the RTM example application:


Figure 2. Vertical mapping database schema

Applying this to your application server requires applying the aggregate entity pattern. Certain application servers do supply a way to implement this pattern; WebLogic Server 7 has a feature called "multiple table mapping," with which a single entity maps to fields coming from two or more related tables. The example I wrote uses this feature.

In the previous diagram, there's a one-to-one relationship between the "base table" and the "subclass tables." The cardinality is 0 or 1 on the side of the base table. In other words, there may be rows of BASE_CUSTOMER with no related rows in GOLD_CUSTOMER or PLATINUM_CUSTOMER. This is how we represent an object of the base class.

The foreign key is placed in the subclass table, and serves also as the key. When dealing with an object of the subclass type, there has to be a corresponding row in the base table and in the subclass table. For example, a gold customer would have a row in GOLD_CUSTOMER and a related row in BASE_CUSTOMER. To access data from the subclasses' tables, use a right-join. What does that mean? For example, to retrieve a gold customer, we will look in the table GOLD_CUSTOMER first, and then only if rows are found, will we look in the BASE_CUSTOMER table.

It is out of the scope of this article to talk about how multiple table mapping or aggregate entity features work in such and such application server. To see an example for WebLogic Server 7, look at the source code provided with this article. Once this step is completed, you are ready to write some code.

The logic in the locate or ejbLocate methods (and even some home methods) must change a bit, compared to horizontal mapping. This is due to the fact that finding a "subclass" entity bean using a given primary key will return a row when looking in its base class' table. For example, imagine a gold customer with primary key "G3." Finding this key in the base class will return a result, although "G3" is not a regular customer.

To fix this problem, all we need to do is to invert the steps (look in subclasses first, and then in the base class), and then check for duplicates:

  • For a locate method returning a single object, find the object in each subclass (using the right finder or locate method each time) until it is found. If no object was found, try in the current bean.

  • For a locate method returning multiple objects, create a list to accumulate the results. Find or locate the objects in each subclass and add them to the list. At the end, find the objects in the current bean and add them to the list, but only if objects with the same primary keys are not already in the list.

Let's see how this is done in the RTM system.

Example 1. Implementing the locate method for a single customer, using vertical table mapping

public BaseCustomer ejbHomeLocate(String customerID) 
         throws FinderException {
    //At this point, all base EJB classes must know their EJB subclasses...
    Context jndiCtx;
    BaseCustomerHome bHome;
    GoldCustomerHome gHome;
    PlatinumCustomerHome pHome;
    try {
      jndiCtx = new InitialContext();
      bHome = (BaseCustomerHome)ctx.getEJBLocalHome();
      gHome = (GoldCustomerHome)jndiCtx.lookup("GoldCustomerEJBVertical");
      pHome = (PlatinumCustomerHome)jndiCtx.lookup
                ("PlatinumCustomerEJBVertical");
    } catch (NamingException e) {
      throw new FinderException("Cannot obtain JNDI initial context.");
    }
    BaseCustomer b=null;
    try {
      b = gHome.findByPrimaryKey(customerID);
      return b;
    } catch (FinderException e) {
    }
    try {
      b = pHome.findByPrimaryKey(customerID);
      return b;
    } catch (FinderException e) {
    }
    try { //Finding in the base class is done last
      b = bHome.findByPrimaryKey(customerID);
      return b;
    } catch (FinderException e) {
    }
    throw new FinderException("Cannot find any bean with that key.");
  }

Example 2. Implementing the locate method for all customers, using vertical table mapping

public Collection ejbHomeLocateAll() throws FinderException
{
    Context jndiCtx;
    BaseCustomerHome bHome;
    GoldCustomerHome gHome;
    PlatinumCustomerHome pHome;
    try {
      jndiCtx = new InitialContext();
      bHome = (BaseCustomerHome)ctx.getEJBLocalHome();
      gHome = (GoldCustomerHome)jndiCtx.lookup("GoldCustomerEJBVertical");
      pHome = (PlatinumCustomerHome)jndiCtx.lookup
               ("PlatinumCustomerEJBVertical");
    } catch (NamingException e) {
      throw new FinderException("Cannot obtain JNDI initial context.");
    }
    //At this point, all base EJB classes must know their EJB subclasses...
    Iterator ib = bHome.findAllBaseCustomers().iterator();
    Iterator ig = gHome.findAllGoldCustomers().iterator();
    Iterator ip = pHome.findAllPlatinumCustomers().iterator();

    Hashtable h = new Hashtable(); //Storing prim-key/entity pairs
    while(ig.hasNext()) {
      GoldCustomer g = (GoldCustomer)ig.next();
      String key = g.getCustomerID();
      h.put(key,g);
    }
    while(ip.hasNext()) {
      PlatinumCustomer p = (PlatinumCustomer)ip.next();
      String key = p.getCustomerID();
      h.put(key,p);
    }
    while(ib.hasNext()) {
      BaseCustomer b = (BaseCustomer)ib.next();
      String key = b.getCustomerID();
      if(!h.containsKey(key)) { //verify the key doesn't exist yet
        h.put(key,b);
      }
    }

    return new ArrayList(h.values());
  }

Here we're using a hashtable to make sure there are no duplicates when processing the base class' query results. There are surely more effective methods, but this one works fine.

Assessment: This kind of table mapping produces a database design closer to the object-oriented equivalent, which is a good idea. But it requires a special feature of the application server, namely, "aggregate entity."

Pages: 1, 2, 3

Next Pagearrow