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