Lazy On-Demand Querying of Detail View Objects

Introduction

A recurring issue when using the automatic master-detail synchronization feature in ADF Business Components is the moment the detail view object is queried. If you have a “deep” or “wide” hierarchy of view object instances in your application module, then the performance cost of those detail queries might add up significantly. This article describes a technique to prevent premature execution of detail queries.

Main Article

In ADF 10, by default all details where queried immediately together with the master during the ADF-JSF prepareModel phase. Steve Muench has provided sample 74. Automatically Disabling Eager Coordination of Details Not Visible on Current Page on his undocumented samples page to delay detail queries when that data is only needed on subsequent pages. This samples works fine in 10.1.3 but is not easy to implement, and the implementation is page specific, not generic. 

Fortunately, in ADF 11 the default query behavior has been changed. For a start, the initial query of a view object is triggered during JSF render response phase while traversing the UI tree. This ensures that only queries that provide data that are actually used on the current page are fired. When a master view object is queried the first time during this JSF render response phase, none of its detail view objects are queried, unless this data is also needed on the same page. This default behavior is already a big improvement over ADF 10, as queries are fired “on-demand” when constructing the page. However, the caviat is that once a detail view object has been queried once, for example because the user navigated from the master page to a detail page that displays this detail information, then on subsequent queries of the master view object, or changes in the current row of the master, the detail view object is queried immediately.

Lets clarify the impact using an example: we have one master view object instance with 5 sibling detail view object instances, all having their own JSF page to display the data. The end user enters the master page, and the master VO is queried, marking the first row as the current row. Then he visits the 5 detail pages for this first master row, causing 5 detail queries, one at the time for each detail page he visits. Now, when the end user returns to the master page and clicks on the second row to make it current, 5 detail queries will fire immediately. If the end user plans to visit the 5 detail pages for this second master row then that’s OK, the queries have to be fired anyway. However, when he will not be visiting the detail pages for this master row, 5 queries have been executed in vain.

From the above description you will understand that ADF BC keeps track of the detail VO’s (detail row sets to be precise) that have been queried before, so it knows whether detail view objects should be queried immediately as a result of a row currency change in the master. If we somehow can “undo” the housekeeping of these detail row sets, we will constantly keep the nice on-demand querying of detail view objects that we get for free for the initial queries. Well, it turns out to be quite simple to implement this “undo” by adding a custom RowSetListener to the master view object. Here is the RowSetListener class:

package oracle.adf.model.adfbc.fwk;

import oracle.jbo.DeleteEvent;
import oracle.jbo.InsertEvent;
import oracle.jbo.NavigationEvent;
import oracle.jbo.RangeRefreshEvent;
import oracle.jbo.RowSet;
import oracle.jbo.RowSetListener;
import oracle.jbo.ScrollEvent;
import oracle.jbo.UpdateEvent;
import oracle.jbo.ViewObject;

public class ClearDetailRowSetsListener implements RowSetListener
{
  /**
   * This method fires when the current row changes.
   * We clean up all detail row sets to avoid querying of these detail row sets
   * when navigating to another master row, or requerying the master
   */
  public void navigated(NavigationEvent event)
  {
    ViewObject vo = (ViewObject) event.getSource();
    RowSet[] rowsets = vo.getDetailRowSets();
    if (rowsets != null)
    {
      for (int i = 0; i < rowsets.length; i++)
      {
        rowsets[i].getViewObject().clearCache();
        rowsets[i].getViewObject().resetExecuted();
        rowsets[i].closeRowSet();
      }
    }
  }

  public void rangeRefreshed(RangeRefreshEvent event)
  {
  }

  public void rangeScrolled(ScrollEvent event)
  {
  }

  public void rowInserted(InsertEvent event)
  {
  }

  public void rowDeleted(DeleteEvent event)
  {
  }

  public void rowUpdated(UpdateEvent event)
  {
  }

}

And here is the view object create method that registers the listener:

protected void create()
{
  super.create();
  addListener(new ClearDetailRowSetsListener());
}

That’s all. You can add this code to your base class view object, and you will get lazy on-demand querying everywhere in your application.

Add Your Comment