Skip to main content

What you may need to know about Nested Application Module

Nesting of Application Module

Oracle ADF provides a modular application development platform where you can logically group the business services in to 'work units'(AKA ApplicationModule) and compose these individual modules to from a larger system based on use case requirements. This feature of composing/nesting ApplicationModules inside a root ApplicationModule is very useful when you build huge enterprise solutions. To learn more about this, please read section 9.4 Defining Nested Application Modulesfrom Fusion Developer's Guide. This blog article talks about a specific use case scenario where a page consumes services exposed from multiple ApplicationModules and examines how the nesting of ApplicationModule eliminates some weird errors and improves system performance

A case study

Let me share an interesting use case scenario related to this topic. In this case, a specific flow was using complex stored procedures to update database tables, and another alternate flow was using EntityObjects to update the same sets of tables. Database Stored procedure was containing some complex business logic to update multiple records across tables. When user clicks on 'Save' button on the screen, this action invokes ApplicationModule's custom method and this in turn calls stored procedure to update tables. If all goes well, AplicationModule commits transaction as last step.
All goes well so far… The real fun starts when a different user transaction tries to update Entities based on those Database Tables which got updated as part of previous transaction (through stored procedure). Unfortunately, the second transaction fails with the below error while committing the changes back to database.

oracle.jbo.RowInconsistentException: JBO-25014: Another user has changed the row with primary key oracle.jbo.Key[SB0000000000231 BC00000013 BA0028 ].
at oracle.jbo.server.OracleSQLBuilderImpl.doEntitySelectForAltKey(OracleSQLBuilderImpl.java:1062)
at oracle.jbo.server.BaseSQLBuilderImpl.doEntitySelect(BaseSQLBuilderImpl.java:548)
at oracle.jbo.server.EntityImpl.doSelect(EntityImpl.java:7762)
at oracle.jbo.server.EntityImpl.populate(EntityImpl.java:6486)
at oracle.jbo.server.EntityImpl.merge(EntityImpl.java:6823)

Google search for this error presented two promising links. These two great posts actually speak in detail about this error and provide some possible solutions as well.

Why Do I Get oracle.jbo.RowInconsistentException?
Bring Back the Hobgoblin: Dealing with RowInconsistentException

Unfortunately, none of the suggestions from these two posts really helped to solve the issue described in the above case.

So what goes wrong here?

The above mentioned use case is making use of both Stored procedures and EntityObjects to update same set of database tables, in different use case scenarios. As you know, ADF BC layer is keeping working copies of entity rows in cache when you retrieve data using ViewObject . While you commit a transaction, Transaction Manager actually scans each entity cache and call commit on dirty entities. If you use stored procedure for committing changes back to database, actually you are by passing built in synchronization mechanism provided by ADFBC to synchronize changes between EntityObjects and Database tables. As the rest of the article is based on this specific scenario, let me try to make it clearer. When we use stored procedure to update database tables, the data modification actually happens outside of ADF BC context. Apparently, EntitytObject in the cache are not aware of this changes and they still hold the stale data. So next time, from a different flow, when you try to commit data using this 'stale' EntityObject, system will go and check whether Entity state is in synch with database table and if it find any mismatch then throws oracle.jbo.RowInconsistentException.
On a related note, please go through Section 40.10.11, How to Protect Against Losing Simultaneously Updated Data from developers guide to learn more about the "lost update" detection for EntityObject

Hmm…So what next?

One obvious solution here is to clear the Entity Cache right after committing the transaction (here, Commit on transaction is called after invoking the Stored procedure). You can either do it by setting Transaction::setClearCacheOnCommit(true); or by Transaction::clearEntityCache(entityName)
Theoretically, the above step is enough to resolve the issue. Interestingly, one team reported saying that in a specific case system still throw the same error, even after clearing the cache.


Oops... what goes wrong again?

Further investigation shows that, this weird behavior is purely because of peculiar implementation approach adopted for realizing the use case. Here, two ApplicationModules were created, one for dealing with all stored procedure related functionalities and the other one to deal data retrieval + manipulation using related ViewObjects and EntityObjects. To make thing worse, these two ApplicationModule were not nested, so both these AapplicationModules were working in their own world. Please note that Entity Cache is kept at Transaction level. So clearing EntityObject cache from one ApplicationModule will never have any effect on cache from a different transaction. This answers the above question. In the above case, 'clearing entity cache' was actually clearing cache from the current ApplicationModule. But there were pages using a different ApplicationModule to get data from same tables using ViewObject. So this ApplicationModule's entity cache went out of synch with database records, when the first transaction commits data using stored procedure

Now, we know the root cause for this erroneous behavior. Time to act...
Obviously, easiest solution here is to nest ApplicationModule and generate a composite service layer by nesting/composing required ApplicaionModules. All of the nested ApplicationModule instances share the same transaction and entity object caches as the root application module that reuses an instance of them.  So if we adopt 'ApplicationModule nesting' approach for the above use case, UI page actually deals with single root ApplicationMdoule and  eliminates the above said erroneous scenario.


Why should you consider nesting ApplicationModule wherever possible?

Advantage is that all nested ApplicationModule participate in same transaction, so same DB connection is used while serving a request (A new transaction implies new DB Connection). As a bonus you get more modular, scalable and robust enterprise application matured enough to meet today's challenges :)

PS: Many thanks to Radhika Mukund, who really helped me in writing this blog article!


Learn More ...

There are a lot more points like this. If  you are curious to learn the internals of the ADF Business Components and ADF Binding Layer,  the following book is for you - Oracle ADF Real World Developer’s Guide.
More details about this book can be found in this post- http://jobinesh.blogspot.in/2012/07/pre-order-your-copy-of-oracle-adf-real.html

Comments

Andreas Koop said…
Very well explained the scenarios in which AMs take part and how they behave. Thanks
Jobinesh said…
Thanks Andreas.
itsolusenz said…
Thank you for this template... web development
Wow...wonderful article explained in simple terms. Useful for ADF newbies also.

Kudos Jobinesh .... Keep posting !
Hasim said…
Thanks Jobinesh and Radhika !! Hat's Off to your passion in writing sucha a wonderful blogs.
simply sue said…
Hi.

Thanks for the article.
My problem is not solved actually. I have an entity empDetails and somehow it got locked. I am forever getting RowInconsistentException.
An attribute I try to set throws an exception. So now how do I solve this problem. Everywhere I read on how to avoid the problem. But how do i unlock the row thats been affected.
Jobinesh said…
Hi,
You are not expected to do release a lock explicitly, rather you can opt out from 'locking' by setting the lock mode as 'none' - only if you don't needn't to deal with transaction sensitive data, ideally mode mode needs to be 'optimistic' or 'optupdate' for web application. If you need locking and want to find the root cause for the RowInconsistentException, then probably the following points may help you
1.Please check whether your EO has a attributes populated by DB trigger(update/insert).If yes, set the right 'Refresh After' property for this attribute.
2. Please see this post http://www.avromroyfaderman.com/2008/05/bring-back-the-hobgoblin-dealing-with-rowinconsistentexception/, thttp://www.avromroyfaderman.com/2008/05/bring-back-the-hobgoblin-dealing-with-rowinconsistentexception/
This link contains some code snippet which can be used to identify the attribute which cause the RowInconsistentException
Swapz said…
Great Article. Thanks for sharing this.
@@Ram@@ said…
Hello Sir ,
I am New user of ADF can you please tell me how to divide the page into three parts.Actualy My requirement is I need to divide page into 3 parts that is header part menu amd body header part must be constant and when I will click on menu respective information will display on the body part..!!

Thanks

R@M

Popular posts from this blog

How to set Bind Variable Values at runtime ?

In this post I'm sharing a couple of approaches for programmatically setting bind variables values at run time. This post is an attempt to explain 'When to use what ?'[ In case if you are familiar with 'Bind Variables' in ADF BC, please refer Section 5.10, Working with Bind Variables in Fusion Developer's Guide ]

1. Set the Bind Variable value using RowSet::setNamedWhereClauseParam(...)

You can use use the setNamedWhereClauseParam(...) method on the ViewObject interface (which extends oracle.jbo.RowSet) to set the value for bind variables. Please note this sets the value on default RowSet. In other words, this doesn't have any effect on the secondary RowSets that you/system generates.
ViewObject vo = am.findViewObject("EmployeesView1"); vo.setNamedWhereClauseParam("bindVarDeptId", new Number(10)); vo.executeQuery();
2. Set the Bind Variable value using ViewObject's VariableValueManager::setVariableValue(...)

VariableValueManager Ma…