Using View Objects in Entity Objects for an Efficient Existence Check

Send me a mail
 Dive into Oracle ADF   Click to see the XML version of this web page.   (Updated: 2/3/2008; 9:24:08 PM.)
Tips and tricks from Steve Muench on Oracle ADF Framework and JDeveloper IDE

Using View Objects in Entity Objects for an Efficient Existence Check

Here's a quick overview of using view objects inside entity objects. Let's say you want to implement an efficient existence check for a particular entity. You can do it using the findByPrimaryKey() method on the entity's definition object, but one thing to consider is that when you find an entity by primary key, all of its attributes are brought into the cache. If you don't want this, you can do a quick check using a SQL statement in a view object that you make use of from your entity object's code. Several good reasons to use a design-time-created View Object instead of dynamically creating the SQL statement at runtime are:

  1. You incur no runtime overhead to describe the structure of a query for a design-time VO
  2. The SQL that you're using in your application is neatly encapsulated into the view object instead of appearing as strings in your Java code
  3. BC4J makes optimal use/reuse of the PreparedStatement, important if you'll be executing the SQL over and over
  4. Takes less code.

In a package x.y.z, I have an entity Employee. Since the findByPrimaryKey() method lives on the EntityDefImpl "entity definition object", which plays the role in the BC4J framework of a "home" or "factory" for an entity object, I choose to have a custom EmployeeDefImpl.java class on the Java panel of the Employee. This way I can add additional "factory" level methods there. In the EmployeeDefImpl.java class I add my method:

/* In File: EmployeeDefImpl.java
 * -------
 * Custom existence check method for Employee entities
 */
public boolean exists(DBTransaction t, Number id) {
  boolean foundIt = false;
  ViewObject vo = getEmployeeExistsView(t);
  vo.setWhereClauseParam(0,id);
  vo.setForwardOnly(true);
  vo.executeQuery();
  foundIt = (vo.first() != null);
  /*   
   * If we didn't find it in the database, try checking against any new employees in cache
   */
  if (!foundIt) {
    Iterator iter = getAllEntityInstancesIterator(t);
    while (iter.hasNext()) {
      EmployeeImpl e = (EmployeeImpl)iter.next();
      /*
       * NOTE: If you allow your primary key attribute to be modifiable
       *       then you should also check for entities with post-state
       *       of Entity.STATUS_MODIFIED here as well.
       */
      if (e.getPostState() == Entity.STATUS_NEW && e.getEmpno().equals(id)) {
        foundIt = true;
        break;
      }
    }
  }  
  return foundIt;
}

This entity definition method makes use of a view object to check for the existence of an employee in the database. If it doesn't find the employee in the database, as a second check it iterates over the Employee entities presently in the cache, and checks if among any of the entities that have the STATUS_NEW status, whether we have a match. If, as is common, our primary key attribute is only Updatable While New, then we only look at the STATUS_NEW entity instances since if they had any other status, we would have found them in the database in the previous step. However, if the primary key attribute is updateable, then this cache iteration should also consider the entities with STATUS_MODIFIED as well to account for entity instances whose primary key has been updated in the current transaction, but not yet posted.

My x.y.z.EmployeeExistsView is defined as an expert-mode view object with no related entities. You can do that by clicking (Next>) enough times in the New View Object wizard to get to the Query panel, and then paste in a statement like this:

select null as X from emp where empno = :0

To further optimize the network traffic, I set the Max Fetch Size to 1 since this query will never return more than 1 row. This saves a network round trip to go decide whether there are more rows to fetch or not.

There are two ways you can use a view object from within an entity object context:

  1. You can call getDBTransaction().createViewObject("x.y.z.EmployeeExistsView"), use it, then call remove() on the view object instance you create, or
  2. You can call getDBTransaction().getRootApplicationModule() and then call findViewObject("SomeViewInstanceName") on the root application module to reuse an existing, well-known view object instance. If that call returns null you can call createViewObject("SomeViewInstanceName","x.y.z.EmployeeExistsView") on that root application module which you then will be able to find by name the 2nd and subsequent times.

The second approach -- only available using BC4J 9.0.3 and above -- has the benefit that the BC4J framework can keep the JDBC prepared statement around and reuse it over and over for different existence checks, differing each time only in the value of the bind variable that you set to be looked up. The code for the helper method getEmployeeExistsView(...) looks like this:

private static final String EMP_EXISTS_USAGENAME = "x_y_z_Employee_Exists";
private static final String EMP_EXISTS_DEFNAME = "x.y.z.EmpExistenceCheck";
/* In file: EmployeeDefImpl.java
 * ------- 
 * Helper method to find (or create the first time) an expert-mode
 * view object that tests whether or not an employee exists
 */
private ViewObject getEmployeeExistsView(DBTransaction t) {
  ApplicationModule root = t.getRootApplicationModule();
  ViewObject vo = root.findViewObject(EMP_EXISTS_USAGENAME);
  if (vo == null) {
    vo = root.createViewObject(EMP_EXISTS_USAGENAME,EMP_EXISTS_DEFNAME);
  }
  return vo;
}

With this code in place, we can leverage the new exists() method on the Employee entity definition object from anywhere we want to test for the existence of an Employee as follows.

  • If our code is inside the EmployeeImpl.java file, we can write:
    EmployeeDefImpl home = (EmployeeDefImpl)getDefinitionObject();
    if (!home.exists(getDBTransaction(),value)) {
      throw new ValidationException("Employee {0} does not exist!","12345",new Object[]{value});
    }
  • If our code is in some other *Impl.java file, we can do:
    EmployeeDefImpl home = (EmployeeDefImpl)EmployeeImpl.getDefinitionObject();
    if (!home.exists(getDBTransaction(),value)) {
      throw new ValidationException("Employee {0} does not exist!","12345",new Object[]{value});
    }
Tip:

In case it comes in handy, another way of finding the definition object for an entity in a generic way is to use:

EmployeeDefImpl home = (EmployeeDefImpl)EntityDefImpl.findDefObject("x.y.z.Employee");

Here we saw a use for an existence check, but any kind of adhoc SQL that you need to do as part of your business logic can be code using similar techniques. The difference would be that the view object would likely get directly used from your entity implementation class instead of the usage in the entity definition implementation class as we've illustrated here.

It just so happens that I created my entity and my existence-check view object in the same x.y.z package, but there is no rule that says they must be in the same package. You can organize all of your BC4J components into packages in the way that works best for you.

Note:

We're considering implementing a generic, efficient existence check as a base BC4J framework feature for a future release.



© Copyright 2008 Steve Muench. Click here to send an email to the editor of this weblog.
Last update: 2/3/2008; 9:24:08 PM.