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" 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="220.127.116.11.23" ClearCacheOnRollback="true" RequeryOnCommit="true" ComponentClass="model.AppModuleImpl" ComponentInterface="model.common.AppModule" ClientProxyName="model.client.AppModuleClient">