ONJava.com    
 Published on ONJava.com (http://www.onjava.com/)
 See this if you're having trouble printing code examples


EJB Inheritance, Part 2

by Emmanuel Proulx
09/25/2002

Implementing EJB inheritance is not straightforward. In my previous article, I described a technique to implant inheritance into entity bean objects, by emulating inheritance-like behavior in some areas. These areas are create and postCreate methods, home methods, and finder and ejbSelect methods.

I must admit, hacking inheritance behavior into these methods is not natural. This is especially true for the finder and ejbSelect methods, which don't behave polymorphically. In other words, finding objects of the base class doesn't fetch objects of subclasses. I have shown you how to work around this problem using a technique I call locate methods.

A locate method must know the following in order to perform its duty:

The two first items are simple; either you know this information or you will find out. The last item is not so simple to grasp.

In This Series

EJB Free and Open Source Tools Summary
What's the best platform for J2EE development? Emmanuel Proulx finds himself answering that question time after time. In this article, he explores several free-as-in-speech and free-as-in-beer EJB 2.0 tools and gives his suggestions for choosing an application server.

EJB Inheritance, Part 4
This series has demonstrated all sorts of ways to handle inheritance in beans. With web and message services, though, how do you handle inheritance with remotely-invoked beans? The EJB 2.0 specification allows it; Emmanuel Proulx demonstrates how.

EJB Inheritance, Part 3
Session beans can take advantage of inheritance, just like entity beans. Indeed, implementing session bean inheritance is nowhere near as hard as it is with entity beans. Part 3 of this series shows the proper technique for implementing inheritance in session beans and addresses the use of factories.

EJB Inheritance, Part 1
The principles of object-oriented programming are encapsulation and inheritance. Enterprise JavaBeans handle encapsulation just fine, but what about inheritance? In this article, the author attempts to apply inheritance to EJBs.

About Table Mapping

Table mapping is the action of connecting in-memory objects to database tables. In the case of entity beans, we're mapping the bean's fields to table columns. In the EJB world, table mapping is not standard at all. Vendors implement this differently.

Yet there are general trends, or options, to map a hierarchy of entity objects to database tables. Choosing which option works for you depends on:

Now let's explore the various options.

Table Mapping Options

There are three different ways to map a hierarchy of EJBs to a database (that I'm aware of):

  1. Horizontal mapping: each object maps to completely separate tables.
  2. Vertical mapping: objects map to a hierarchy of tables linked with relationships, to avoid duplicate columns.
  3. One-table mapping: objects map to a single table, with a "category" field.

Table 1 offers a summary of the three table mapping options, and their characteristics:

Table 1. Table mapping options, characteristics

Characteristics Horizontal Vertical One-table
Number of tables One per class One per class 1
Relationships? No Yes No
Repeated columns? Yes No No
"Category" column? No No Yes

Let's explore each table mapping alternative, mention how they are used in our RTM example, and provide code that supports them. We'll also discuss when each alternative is appropriate.

Horizontal Mapping

Horizontal mapping was covered in the first article of this series. The database schema is composed of disconnect tables, one for each class. This diagram illustrates the database schema used in our RTM example:


Figure 1. Horizontal mapping database schema

As mentioned in part one of this series, the code for locate methods follows this logic:

Refer to the first article for the source code snippets (the methods ejbHomeLocate() and ejbHomeLocateAll()). This code is also included in the zip file for this article.

NOTE: One thing I forgot to mention in the previous article: it makes more sense to use the subclasses' locate methods to recursively go through the whole hierarchy, instead of using finders. In my examples, subclasses didn't need any locate methods because they were "leaves" in that hierarchy. But it may be a good idea to write locate methods for all classes, even for those "leaf" classes. Such locate methods would just call the finders directly. This is done for consistency, but also, if new classes are added under a "leaf" class, you won't have to modify its base class.

Assessment: This is the most portable way to implement EJB inheritance, with the simplest table-mapping configuration.

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:

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."

One-Table Mapping

As its name indicates, one-table mapping involves a single table, including rows belonging to all classes of the hierarchy. This single table must then have a special column to specify what kind of object is being stored in the rows. This column can bear many names: category, type, kind, class, variety, brand, sort, etc.

Here's the database schema for our RTM database.


Figure 3. One-table mapping database schema

We've introduced the Category field here, which can hold one of three values: "B" (base customer), "G" (Gold customer), and "P" (Platinum Customer). This field is pretty much useless in our business logic, but we still need to declare it to code the queries, as we'll see shortly. Don't forget to initialize this field in ejbCreate(), for the base class and all subclasses. The BaseCustomerBean class sets the field to "B," the GoldCustomerBean class sets the field to "G," and the PlatinumCustomerBean class sets the field to "P."

In this kind of table mapping, a single table contains all fields of all objects in the whole hierarchy. Fields that are not used in a certain row are simply left null. For example, the Charity field is not used by base and gold customers. This technique does produce a lot of null fields, but it shouldn't be a problem.

The locate methods for this table-mapping technique are the same as in the horizontal mapping, so I'll skip this part.

But there is one problem introduced by this table mapping. Finder methods don't behave as they should. Imagine a situation where there's a regular customer with customer ID of "123." Finding a gold customer using this same key will return a (wrong) result! There has to be a way to filter out the other kinds of customers when querying the database. In this example, it would mean ignoring regular and platinum customers when finding a gold customer.

Working around this problem is easy, in most cases. Just add a condition at the end of every EJB-QL query, like this:

EJB-QL Query for Base customers:

SELECT OBJECT(c) FROM BaseCustomerEJBOneTable AS c 
    WHERE c.category = 'B'

EJB-QL Query for Gold customers

SELECT OBJECT(c) FROM GoldCustomerEJBOneTable AS c 
    WHERE c.category = 'G'

EJB-QL Query for Platinum customers

SELECT OBJECT(c) FROM PlatinumCustomerEJBOneTable AS c 
    WHERE c.category = 'P'

But what about findByPrimaryKey()? The container writes this method for you. How do you filter out the unwanted rows? Once more, the only way to work around this problem is to rely on a feature of your application server.

WebLogic Server 7 allows overwriting findByPrimaryKey() using a WebLogic-QL query in weblogic-cmp-rdbms-jar.xml. I'm using this trick in my RTM example. Other vendors may tackle this in other ways. Some may allow manually writing this query in ejb-jar.xml, although that may go against the specification. Some may not support this feature at all, which makes one-table mapping impossible.

Related Reading

Enterprise JavaBeans
By Richard Monson-Haefel

Assessment: This kind of table mapping is often used with a legacy database, which typically has tables with category columns. It requires that the application server provide a way to filter out the rows that are not wanted when running the queries.

Conclusion and Acknowledgments

I didn't invent any of this. Many products exist out there which allow us to use these three techniques (and others) when mapping database tables to hierarchies of classes. I merely applied the three techniques to the special case of entity beans, working around problems as I went.

I specifically want to credit Apple's Enterprise Object Framework (EOF) toolkit for its ingenuity in this field, which inspired me. EOF is a library and tools to map database tables to straight Java (and Objective-C) objects. The maturity of this framework, its flexibility, and its simplicity should be a model for any persistence services provider.

Also worth highlighting, WebLogic Server 7 provided me with all the features needed in order to implement my work-arounds. Kudos to BEA.

Don't get cranky if I didn't mention your favorite persistence services provider tool. I know there are others out there, I just can't explore them all in these few lines.

Next time, we'll see examples of session beans and message-driven beans that use inheritance.

Emmanuel Proulx is an expert in J2EE and Enterprise JavaBeans, and is a certified WebLogic Server 7.0 engineer. He works in the fields of telecommunications and web development.


Return to ONJava.com.

Copyright © 2009 O'Reilly Media, Inc.