Using a general superclass to refresh SQL-calculated attributes in your dirty ADFBC View Object

3

In our current ADF10g project we have many refresh issues. After an insert or update some view object attribute values are not shown. Many attributes of our entity based view objects get their value from the SQL statement that queries the data of the view object (SQL-calculated attribute). After an insert or update, view objects with one or more SQL-calculated attribute needs to be requeried if you want their values to be evaluated again. In this article I will discuss how to make a general superclass that refreshes the data of a View object, but first checks whether this is necessary or not. This superclass can detect if a view object is ‘dirty’ – an entity based view object is dirty in this article when any inserts or updates for the underlying entity objects are about to be posted to the database. If yes, this superclass will requery the view object after the database commit, and at the same time all SQL-calculated attribute values will be evaluated again. This superclass could also be used in general, to refresh a view object in cases when this is needed.
 

Requery is needed with SQL-calculated attributes

View objects can include SQL-calculated attributes. A calculated attribute is an attribute that gets its value from the SQL statement that queries the data of the View object. Therefore, anything you can conjure up in the “SELECT” part of an SQL statement can be used to fill this attribute, from simple concatenations or decodes to PLSQL function calls and entire sub-SELECT statements.
 

 

 

After an insert or update of an employee, the query needs to be executed again if we want to see the value of the calculated attribute:

 

 

 

Requery is sometimes needed when many attributes are set by database triggers

If you know that the underlying column value will be updated by a database trigger during insert or update operations, you can check the ‘refresh after insert’ or ‘refresh after update’ checkboxes to have the framework automatically retrieve the modified value to keep the entity object and database row in sync. The entity object uses the Oracle SQL RETURNING INTO feature, while performing the insert or update to return the modified column back to your application in a single database round-trip.
 

 

 

In nearly most cases this works very well. However, in some special cases this does not seem to work. For example, this happened in our case when we made a custom implementation (override) of some of the key ADFBC framework methods of an EntityImpl class. We had defined several updatable view objects that were referencing attributes from more than one updatable entity object, and we had overridden some key methods of the <EntityName>Impl.java like the postChanges() and refreshFKInNewContainees() method. In our special case, the ‘refresh after insert’ or ‘refresh after update’ did not update the value, and a requery was needed.
 

Flow of the ADFBC View Object

For a requery, we could call the standard view object method executeQuery(), but then a view object will lose its current row – after a call to executeQuery() on the view object, the current row will always be reset to the first row your view object iterator points to. If you need to keep the current row you can use a refresh-but-stay-where-you-were requery method ‘refreshQueryKeepingCurrentRow()’ Steve Muench described in the afterCommit() method of your the view object. A big disadvantage of calling an executeQuery() or a refreshQueryKeepingCurrentRow() in the aftercommit() method is, that the requery will always be executed, even when their were no changes at all to the data of the view object. I tested this and noticed that the ADFBC framework method afterCommit() is called for every view object that has already been queried once in the current session, even for view objects that had nothing at all to save to the database. Apparently the ADFBC framework flow calls the afterCommit() method for every view object that has already been queried once (and for which entity objects exist in the entity cache). If we requery all these view objects, we will unnecessary query many of them again that did not have any changes at all. Of course this is not what we want. We need to recognize in the afterCommit() method whether the data of the view object was ‘dirty’ or not before we requery it; did the view object had any new or updated underlying entity objects for which data was posted and committed to the database?

 

Let your entity objects register themselves as dirty on the oracle.jbo.Session object

How can we recognize a dirty view object? Unfortunately, a method like ‘isDirty()’ does not exist on the ViewObjectImpl class. Neither does a method like getPostState() on the ViewRowImpl class that calls its status. But on the EntityImpl class we can get the status of an entity object by calling the getPostState() method, for example in the overridden framework method postChanges(). The EntityImpl framework method postChanges() will always be called when attribute values of a modified or new entity object are posted to the database. Your EntityImpl custom base class is a good place to override this postChanges() method and set a property on the session object that indicates that for this entity type dirty entity objects exists. A custom base class extends an ADF BC framework base class – for entity objects oracle.jbo.server.EntityImpl – to add a generic feature that all of your view objects need. This is a convenient place in the class hierarchy to add our overridden postChanges(), so now this method is called automatically for all your entity objects when they are new or have any changes. An oracle.jbo.Session object (NOT javax.servlet.http.HttpSession) represents a root application module’s session, and an implementation of this interface is instantiated for each root application module. The session object is garbage collected when the root application module is garbage collected. This is exactly the scope we need for our dirty entity objects. First we can check in the postChanges() method whether the status of an entity object is new or modified by calling the getPostState():

 

if (getPostState() == STATUS_NEW) {…} 

if (getPostState() == STATUS_MODIFIED){…}

 

and call the session object from the DBTransaction object:

 

Session session = transactionEvent.getDBTransaction().getSession();

 

Then we can get the userdata Hashtable object of the session by calling session.getUserData(), and set the current entity as being dirty by adding the current entity definition name to the session properties in the userData hashtable:

 

session.getUserData().put(getEntityDef().getName(),"dirty");

 

Our complete EntityImpl custom-base class:

 

 

package HR.model.framework;
import oracle.jbo.Session;
import oracle.jbo.server.EntityImpl;
import oracle.jbo.server.TransactionEvent;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

public class HREntityImpl extends EntityImpl {
    public HREntityImpl() {}
    private static Log sLog = LogFactory.getLog(HREntityImpl.class);   
        
     /**
     * Check whether the status of an entity object is new or modified by calling the getPostState().
     * If the status is new or modified, call the session object from the DBTransaction object.
     * The userdata hashtable of the session is retrieved by calling session.getUserData(),
     * and then the current entity is set as being dirty by adding the current entity definition name
     * to the session properties in the userData hashtable.
     */
    public void postChanges(TransactionEvent transactionEvent)
    {   
        if (getPostState() == STATUS_NEW)
        {
            sLog.debug(getEntityDef().getName()+" in postChanges()....STATUS_NEW");
            Session session = transactionEvent.getDBTransaction().getSession();
            session.getUserData().put(getEntityDef().getName(),"dirty");      
        }
        if (getPostState() == STATUS_MODIFIED)
        {
            sLog.debug(getEntityDef().getName()+" in postChanges()....STATUS_MODIFIED");
            Session session = transactionEvent.getDBTransaction().getSession();
            session.getUserData().put(getEntityDef().getName(),"dirty");
        }
        super.postChanges(transactionEvent);
    }
}

 

 

Make a view object superclass that recognizes when it’s dirty

We have to check for ‘dirtiness’ before a commit is send to the database, because after a commit we cannot check the entity objects anymore whether they were new or had changed attribute values that were committed to the database or not. After a commit the status of all entity objects is ‘STATUS_UNMODIFIED’. We have to do the check before the commit, in an overridden view object framework method beforeCommit():

 

           

    /**
     * Check if any entity object this view object is based on is 'dirty’ in the current transaction.
     * A ViewObject is dirty when any of its underlying EntityImpl objects returns
     * STATUS_NEW or STATUS_MODIFIED on getPostState().
     * They have already been set in the userData hashtable properties of the session object
     * in the custom base class of EntityImpl in postChanges().
     * Now we can check the userData hashtable if it contains any entity object names this view object is based on.
     */
     public void beforeCommit(TransactionEvent transactionEvent)
     {
         super.beforeCommit(transactionEvent);  
        
         //Call the oracle.jbo.Session object from the DBTransaction object:
         Session session = transactionEvent.getDBTransaction().getSession();

         //Make a Set of all the keys (=dirty entity class names)
         Set<String> set = session.getUserData().keySet();
 
         //Get all the EntityDefImpl objects this view object is based on.       
         EntityDefImpl[] entityDefs = getEntityDefs();
       
         //Check the whether there exists dirty EntityImpl objects for this EntityDefImpl class,
         // and if yes, mark this view object as dirty.        
         for(EntityDefImpl entityDefImpl:entityDefs)
         {
              if(set.contains(entityDefImpl.getName()))
              {
                 sLog.debug("Viewobject "+getName()+" will be requeried in afterCommit(), underlying entity "+entityDefImpl.getName()+" is DIRTY...");
                 dirty=true;
              }
         }
     }

 

 

 

First we call the oracle.jbo.Session object from the DBTransaction object and make a set of all the keys that represent the dirty EntityDefImpl class names in the current transaction:

 

Session session = transactionEvent.getDBTransaction().getSession();
Set<String> set = session.getUserData().keySet();

 

Then we get all the EntityDefImpl objects this view object is based on:

 

EntityDefImpl[] entityDefs = getEntityDefs();       

 

Now, this view object checks whether there exists dirty EntityImpl objects for this EntityDefImpl class, and if yes, mark this whole view object as dirty:

 
for(EntityDefImpl entityDefImpl:entityDefs)
         {
              if(set.contains(entityDefImpl.getName()))
              {
                 sLog.debug("Viewobject "+getName()+" will be requeried in afterCommit(), underlying entity "+entityDefImpl.getName()+" is DIRTY...");
                 dirty=true;
              }
         }

 

So if any entity object this view object is based on is new or modified the view object will mark itself as being dirty in the beforeCommit(). Then, in the overridden view object method afterCommit(), a view object can check whether it is marked as dirty or not and in case it’s dirty execute a requery by calling refreshQueryKeepingCurrentRow(). After that, all old dirty EntityDef names must be removed from the session hashtable with userData to be reset for the next transaction:  

 

 

     /**
     * Only requery when an entity this view object is bases on was 'dirty' in beforeCommit().
     * Then, remove all old dirty EntityDef names from the session userData Hashtable object.
     */
    public void afterCommit(TransactionEvent transactionEvent)
    {      
        super.afterCommit(transactionEvent);                      
        if(getCurrentRow()!=null)
        {
            if(dirty==true)
            {
                 sLog.debug("Viewobject "+getName()+" REQUERIED in afterCommit().");
                 refreshQueryKeepingCurrentRow();           
                 dirty=false;
            }
        }   
      
        //Remove all old dirty EntityDef names from the session userData Hashtable object.
        //Get the session’s userData Hashtable object.
        Session session = transactionEvent.getDBTransaction().getSession();
        Hashtable hashtable = session.getUserData();       
       

        //Check if the hashtable contains any dirty entitydef names.
        if(hashtable.contains("dirty"))
        {          
            //Make a set with all the keys of the userData properties.
            Set<String> setKeysUserData = session.getUserData().keySet();
           
            //Make a second set that contains all the names of the old dirty EntityDefs.
            Set<String> setKeysOfDirtyEntities=new HashSet<String>();
            for(String key:setKeysUserData)
            {
                if (hashtable.get(key).equals("dirty")) setKeysOfDirtyEntities.add(key);
            }
           
            //Use the second set to remove the names of the old dirty EntityDefs.
            for(String key:setKeysOfDirtyEntities)
            {
                hashtable.remove(key);
            }
        }
    }  
}   

 

I think it is a good idea to set your own custom project base class of the ViewObjectImpl base class as the superclass of this RefreshQueryAfterCommitViewObjectImpl class. In this code our base class is the HRViewObjectImpl class. Complete code of RefreshQueryAfterCommitViewObjectImpl:

 

 

 

package HR.model.framework;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.Set;
import oracle.jbo.Session;
import oracle.jbo.server.EntityDefImpl;
import oracle.jbo.server.TransactionEvent;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

public class RefreshQueryAfterCommitViewObjectImpl extends HRViewObjectImpl
{
     private boolean dirty=false;
     private static Log sLog = LogFactory.getLog(RefreshQueryAfterCommitViewObjectImpl.class);
    
     public RefreshQueryAfterCommitViewObjectImpl() {}

     /**
     * Check if any entity object this view object is based on is 'dirty’ in the current transaction.
     * A ViewObject is dirty when any of its underlying EntityImpl objects returns
     * STATUS_NEW or STATUS_MODIFIED on getPostState().
     * They have already been set in the userData hashtable properties of the session object
     * in the custom base class of EntityImpl in postChanges().
     * Now we can check the userData hashtable if it contains any entity object names this view object is based on.
     */
     public void beforeCommit(TransactionEvent transactionEvent)
     {
         super.beforeCommit(transactionEvent);  
        
         //Call the oracle.jbo.Session object from the DBTransaction object:
         Session session = transactionEvent.getDBTransaction().getSession();

         //Make a Set of all the keys (=dirty entity class names)
         Set<String> set = session.getUserData().keySet();
 
         //Get all the EntityDefImpl objects this view object is based on.       
         EntityDefImpl[] entityDefs = getEntityDefs();
       
         //Check the whether there exists dirty EntityImpl objects for this EntityDefImpl class,
         // and if yes, mark this view object as dirty.        
         for(EntityDefImpl entityDefImpl:entityDefs)
         {
              if(set.contains(entityDefImpl.getName()))
              {
                 sLog.debug("Viewobject "+getName()+" will be requeried in afterCommit(), underlying entity "+entityDefImpl.getName()+" is DIRTY...");
                 dirty=true;
              }
         }
     }

 

 

    /**
     * Only requery when an entity this view object is bases on was 'dirty' in beforeCommit().
     * Then, remove all old dirty EntityDef names from the session userData Hashtable object.
     */
    public void afterCommit(TransactionEvent transactionEvent)
    {      
        super.afterCommit(transactionEvent);                      
        if(getCurrentRow()!=null)
        {
            if(dirty==true)
            {
                 sLog.debug("Viewobject "+getName()+" REQUERIED in afterCommit().");
                 refreshQueryKeepingCurrentRow();           
                 dirty=false;
            }
        }   
      
        //Remove all old dirty EntityDef names from the session userData Hashtable object.
        //Get the session’s userData Hashtable object.
        Session session = transactionEvent.getDBTransaction().getSession();
        Hashtable hashtable = session.getUserData();       
       

        //Check if the hashtable contains any dirty entitydef names.
        if(hashtable.contains("dirty"))
        {          
            //Make a set with all the keys of the userData properties.
            Set<String> setKeysUserData = session.getUserData().keySet();
           
            //Make a second set that contains all the names of the old dirty EntityDefs.
            Set<String> setKeysOfDirtyEntities=new HashSet<String>();
            for(String key:setKeysUserData)
            {
                if (hashtable.get(key).equals("dirty")) setKeysOfDirtyEntities.add(key);
            }
           
            //Use the second set to remove the names of the old dirty EntityDefs.
            for(String key:setKeysOfDirtyEntities)
            {
                hashtable.remove(key);
            }
        }
    }  
}    

 

 

 

You can add the refresh-but-stay-where-you-were functionality method refreshQueryKeepingCurrentRow() to this class or (better) add it to your own view object custom base class, so that all of your view objects in your project could call this very useful method of Steve Muench:

 

 

       /**
     * refresh-but-stay-where-you-were functionality
     * Steve Muench http://radio.weblogs.com/0118231/2004/11/22.html
     */
    public void refreshQueryKeepingCurrentRow()
     {  
       sLog.debug("refreshQueryKeepingCurrentRow().. ");
       Row currentRow = getCurrentRow();
       Key currentRowKey = currentRow.getKey();
       int rangePosOfCurrentRow = getRangeIndexOf(currentRow);
       int rangeStartBeforeQuery = getRangeStart();
       executeQuery();
       setRangeStart(rangeStartBeforeQuery);
       if (rangePosOfCurrentRow >= 0) {
         findAndSetCurrentRowByKey(currentRowKey,rangePosOfCurrentRow);
       }         
     }

 

 

Set the RefreshQueryAfterCommitViewObjectImpl class as the superclass of your view objects that need a requery

Now you have a general solution for this type of refresh issues. When you are confronted with a refresh issues involving calculated attributes or involving attributes set by database triggers, the only thing you will have to do is to set this class as the superclass of your view object. In the hierarchy, you just place the RefreshQueryAfterCommitViewObjectImpl class between your own custom base class and the view object that you want to refresh:

 

 

 

 

In JDeveloper you can do this in the View Object Editor:

 

 

 

 

Now your dirty view object will be refreshed after an insert or an update.

 

Refreshing your view objects with SQL-Calculated attributes automatically without having to look after them

Alternatively, you even don’t have to set a new superclass for your view objects with SQL-Calculated attributes. Your ViewObject can detect automatically whether it contains SQL-Calculated attributes or not, and only requery when it does. A view object can ask for its attribute definitions and for each attribute definition it’s kind of attribute. If the kind is ATTR_SQL_DERIVED, it’s SQL-Calculated. You could add all the three methods beforeCommit(), afterCommit and refreshQueryKeepingCurrentRow() to your custom ViewObjectImpl base class and add an overridden create() method to it. This method is called by the framework every time a ViewObjectImpl object is created, and a good place for us to check whether this view object contains SQL-Calculated attributes or not:
 

private boolean hasSQLCalculatedAttribute=false;

protected void create()
{
        super.create();
       
//Get an array of all attribute definitions of this ViewObjectImpl.
        AttributeDef[] attributeDefs = getAttributeDefs();
       
//Check whether any attribute definition exists that is SQL-Calculated.
        for(AttributeDef attributeDef:attributeDefs) {
             if(attributeDef.getAttributeKind()==AttributeDef.ATTR_SQL_DERIVED) {
                 sLog.debug("Viewobject "+getName()+" has SQL-Calculated attributes - if dirty, it's requeried after a commit.");
                 hasSQLCalculatedAttribute=true;
                 break;
                }
         }       
}

 

 

Then, in the afterCommit() method, before the requery, also check whether it has SQL-Calculated attributes or not:

 

if(getCurrentRow()!=null)
    {
    if(dirty==true)
       {
         if(hasSQLCalculatedAttribute==true)
            {
              sLog.debug("Viewobject "+getName()+" REQUERIED in afterCommit().");
              refreshQueryKeepingCurrentRow();           
            }
            dirty=false;
       }
     } 

 
Now all your view objects with SQL-Calculated attributes are refreshed automatically without having to look after them any more.
 

 

 

 

 

Share.

About Author

Frank Houweling is an Oracle ADF and Java specialist with AMIS (The Netherlands). He focuses mainly on Oracle Fusion ADF, Java Enterprise development and performance management. During the past years he has been requested several times as troubleshooter of ADF projects with bad performance. As such he has been performing performance analysis, bottleneck detection and developing mitigating solutions based on these analysis. He is also the creator of the AMIS ADF Performance Monitor, an advanced monitor that can identify, report and help solve performance bottlenecks in ADF applications.

3 Comments