Yet another reason for "JBO-25014: Another user has changed the row with primary key oracle.jbo.Key"

I've discussed one possible (tricky) reason for JBO-25014 error in one of my previous post - 'What you may need to know about Nested Application Module' . Recently I noticed yet another interesting reason for the above said JBO-25014 error. Let me summarize the same for the benefit of the ADF community.

Use case

This use case has two view objects - SimpleEmployeeViewObject and DetailedEmployeeViewObject, both are based on the same entity object - EmployeeEntityObject. A specific business functionality is implemented using the above ViewObjects as listed below.

1. SimpleEmployeeViewObject queries the DB by calling SimpleEmployeeViewObject.executeQuery(). The code doesn't fetch any records at this stage(I meant that, this code just got the executeQuery call, no iteration logic). However this call results in the creation of result set/cursor, and leaves it opened.

2. Then, DetailedEmployeeViewObject queries by calling DetailedEmployeeViewObject.executeQuery(). Here the code fetches set of records and modifies the record with EmployeeID = 101.

3. Commits the Transaction, and then call DetailedEmployeeViewObject.clearCache()

4. As a next step, SimpleEmployeeViewObject tries to get the employee record with EmployeeID = 101. Note that, SimpleEmployeeViewObject does not call executeQuery here, instead queries the RowSet(retrieved at Step 1) for the record. This may return the stale row from the opened cursor, and apparently this row doesn't reflect the modified attributes from database (committed at Step 3). This step modifies the retrieved record and commits the transaction.

5. At Step 4, the 'commit' operation may tries to locks the row, it compares the original values of all the persistent attributes in the entity cache as they were last retrieved from the database with the values of those attributes just retrieved from the database during the lock operation. Apparently this may throw "JBO-25014: Another user has changed the row with primary key oracle.jbo.Key[101]" as the entity object is populated with a stale row set.

What is the solution ?

The solution is to close the opened cursor(result set) having the stale data after the commit at Step3 ( before the next transaction). Please note that, ADF BC runtime closes the result set/curosr as part of Transaction::rollback(). However Transaction::commit() doesn't touch the opened cursors, by default.

The easiest work around here is to call SimpleEmployeeViewObject.executeQuery() before step4. Either you can do it through a explicit call or you can move this call to SimpleEmployeeViewObjectImpl::afterCommit(TransactionEvent event) method.

Re-querying all View Objects on commit

On a related note, you can configure a specific Application Module to refresh all its View Objects after the commit of a transaction by setting RequeryOnCommit="true". This setting may cause all the 'queried' View Objects of the Application Module to requery/refresh after the commit. This setting also may help you to solve error that we discussed in this post (You should be careful while keeping this ON as this result in expensive requery of all View Objects belonging to the Application Module).

 <AppModule  
  xmlns="http://xmlns.oracle.com/bc4j"  
  Name="AppModule"  
  Version="11.1.1.59.23"  
  ClearCacheOnRollback="true" RequeryOnCommit="true" 
  ComponentClass="model.AppModuleImpl"  
  ComponentInterface="model.common.AppModule"  
  ClientProxyName="model.client.AppModuleClient">  

Comments

Til said…
Can you upload an example to try this.
Jobinesh said…
Til, This is a very odd scenario. Lots of factors needs to be aligned to reproduce the issue :) Bottom line is that, You don't need to worry on this, if you never happened to encounter this error. However, just for the sake of answering this query, I copying the relevant test code below for your ref(Thanks to Sekhar Korupolu for sharing this)

ViewObject simpleEmployee = getSimpleEmployeeViewObject();
simpleEmployee.executeQuery();
ViewObjectImpl detailedEmployeeVO = getDetailedEmployeeViewObject();
Key k = detailedEmployeeVO.first().getKey();
detailedEmployeeVO.first().setAttribute("LastName", "ModifiedOne");
DBTransactionImpl txn =
(oracle.jbo.server.DBTransactionImpl)this.getTransaction();
detailedEmployeeVO.clearCache();
txn.commit();

oracle.jbo.server.EntityCache ec =
txn.lookupEntityCache(oracle.jbo.server.EntityDefImpl.findDefObject("model.EmployeesEntityObject"));
System.gc();
while (ec.getForAltKey(0, k) != null) {
System.gc();
try {
Thread.sleep(1000);
} catch (Exception e) {
}
}
simpleEmployee.first().setAttribute("LastName", "ModifiedOne@@@");
txn.commit();
Brother said…
Hi Jobinesh,

This is regarding a problem with my project. The application behaves unexpectedly on a button click(not necessarily same button) event and a particular screen appears. This is found on different modules. Could you pls help me in identifying the root cause?
Jobinesh said…
Well, there are multiple reasons for this.
One common root cause is that some EO may have attributes populated by DB trigger or sequence which is not getting refreshed back on specif EO instance. You may need to turn on 'Refresh After' flag for the EO in this case. Another reason is detailed in one of my previous case.
Also check this link http://www.avromroyfaderman.com/2008/05/bring-back-the-hobgoblin-dealing-with-rowinconsistentexception/ , this contains some sample code to identify the attribute that triggers this exception
http://jobinesh.blogspot.com/2010/03/what-you-may-need-to-know-about-nested.html
This comment has been removed by the author.
Can you please help me out with this case:
https://forums.oracle.com/forums/thread.jspa?messageID=9852928#9852928
Jobinesh said…
Divya,
If the issues doesn't happen after re executing the query, then the root cause could be same as described in this blog. Probably you may need to re-execute VO and work around other issues
Tr0nx said…
This comment has been removed by the author.
Tr0nx said…
My solution to the problem was to add the following line to the Application Module:

ClearCacheOnCommit = "true"

I was as follows:

AppModule xmlns = "http://xmlns.oracle.com/bc4j"
Name = "UtdAM"
Version = "11.1.1.60.13"
ClearCacheOnRollback = "true"
ClearCacheOnCommit = "true"

I hope they serve.
vinay.polisetti said…
Thanks Jobinesh, this really helped.
sidhu said…
Thank you very much for the post.I had this similar error, found the issue was with VO. somehow it got messed up.
ADF OAF Tech said…
Thanks Jobinesh Sir it worked.
public void afterCommit(TransactionEvent transactionEvent) {
super.afterCommit(transactionEvent);
executeQuery();

}
bala reddy said…
How this will work with SDO service interface?
you could try:
row.refresh(Row.REFRESH_WITH_DB_FORGET_CHANGES);
I think this is not so documented, but it may help you in order to refetch the row from database, then you may be able to perform some change and then commit.

Disclaimer

The views expressed on this blog are my own and do not necessarily reflect the views of my employer.