I encountered a hibernate 2nd layer cache consistency issue when rolling back hibernate-managed automatic versioned entity
Scenario (specific versions, etc. see below):
1. POJOs with one-to-many parent-child relationship (e.g., Invoice -> LineItems), the objects are cached with hibernate 2nd layer cache
2. Hibernate version-based optimistic locking is used on the top level entity (e.g., invoice)
3. Thread 1 added a line item to the an invoiceA, brought the version of invoiceA from 0 to 1
4. Thread 2 tried to add another line item to invoiceA and save the invoice (cascading to lineItems) based on the
stale version 0 of invoiceA.
The logic hit an optimistic locking exception (StaleObjectStateException) on invoice entity :
4a. hibernate (as part of the transaction rollback) invalidated the cache entry for invoiceA
4b. However, hibernate did not invalidate the cache entry for the lineItems collection (which is counted as part of the versioning of invoiceA entity based on the mapping file)
5. When thread 2 retries (add a lineItem to invoiceA and save),
5a. it is correctly manipulating with the up-to-date invoiceA entity version 1 (as the stale version 0 is cleared from cache)
5b. However, it is manipulating with a out-of-date lineItem collection (see step 4b above), i.e., it does not see the line item added by thread 1.
In my use case, sometimes above save in step 5 will end up creating a corrupted entity that violates domain business rules.
Has anyone encountered a similar issue? If so, how do you resolve it?
What I've traced so far indicated that during the rollback of saving entity ( hibernate EntityUpdateAction#afterTransactionCompletion(boolean) ),
* the logic invalidates the entity cache entry (step 4a above),
* but does not invalidate the cache entry of the collection object (which based on our hbm, the collection object is versioned as part of the parent entity).
One solution is to patch hibernate code (as EntityUpdateAction is not exactly part of the extensibility API) but before going down that path, we'd like to see if there is some other way to resolve the issue.
Thanks!
Hibernate version: 3.2.2 ga, nonstrict readwrite 2nd layer cache (OSCache provider).
Mapping documents:
Code:
<class name="Invoice" select-before-update="true" >
<cache usage="nonstrict-read-write"/>
<id name="id" column="inv_id" unsaved-value="0">
<generator class="native"/>
</id>
<version name="version" />
...
<set name="lineItems" inverse="true" lazy="false" cascade="all-delete-orphan" optimistic-lock="true" batch-size="20">
<cache usage="nonstrict-read-write"/>
<key column="inv_id"/>
<one-to-many class="LineItem"/>
</set>
...
</class>
Name and version of the database you are using: Should be irrelevant. reproduced with both Mysql 5 and Oracle 10
Note: Spring transaction manager is used to manage hibernate session but it should be irrelevant.