We have a legacy Spring Hibernate application that utilizes XML-based Hibernate configuration. Intermittently, when calling an operation concurrently, we encounter the
javax.persistence.OptimisticLockException: Batch update returned unexpected row count from update [0]; actual row count: 0; expected: 1.
I understand that we typically encounter OptimisticLockException when making concurrent requests to the database if multiple transactions access the same table. However, in my case, it's within a single transaction. Let me share the mappings and code to explain better.
User.hbm.xml:
<class lazy="false" name="UserBO" table="USER">
<id name="id" type="java.lang.Long" column="ID" unsaved-value="null">
<generator class="native"/>
</id>
<many-to-one name="addressSet" column="ADDRESS_SET" class="AddressSetDO"/>
</class>
AddressSet.hbm.xml:
<class lazy="false" name="AddressSetDO" table="ADDRESS_SET_TABLE">
<id name="id" type="java.lang.Long" column="ID" unsaved-value="null">
<generator class="native"/>
</id>
<version column="HIB_VERSION" name="HibVersion"/>
<set name="address" inverse="true" lazy="false" cascade="delete">
<key column="ADDRESS_SET_ID" />
<one-to-many class="AddressDO"/>
</set>
</class>
Address.hbm.xml:
<class lazy="false" name="Address" table="ADDRESS">
<id name="id" type="java.lang.Long" column="ID" unsaved-value="null">
<generator class="native"/>
</id>
<version column="HIB_VERSION" name="HibVersion"/>
<many-to-one name="addressSet" not-null="true" class="AddressSetDO" index="ADDRESSSET" inverse="true" >
<column name="ADDRESS_SET_ID" not-null="true" /> </many-to-one> </class>
Code flow: From the main class (due to complex logic, we tried to call the save method three times inside the transaction): Service class:
Transaction start
// code logic
dao.save(data);
// code logic
dao.save(data);
// code logic
dao.save(data);
Transaction end
Dao class (even though we have mapping, we manually save the mapping table):
saveOrUpdate(userBo); //userBo
if(userBo.getAddressSet() != null)
saveOrUpdate(userBo.getAddressSet()); //AddressSet
saveOrUpdate(userBo.getAddressSet().getAddress()); //Address
And my saveOrUpdate code is:
void saveOrUpdate(obj) {
try {
hibernateSession().saveOrUpdate(obj);
checkReadOnlySession();
hibernateSession().flush();
} catch (PersistenceException e) {
throw e;
}
}
Note: The logic has been in place for many years, and attempting to change it would entail significant risk.
Now, if I keep calling the functionality with separate data, I intermittently encounter the following issue from saveOrUpdate(userBo.getAddressSet()):
javax.persistence.OptimisticLockException: Batch update returned unexpected row count from update [0]; actual row count: 0; expected: 1 at org.hibernate.internal.ExceptionConverterImpl.wrapStaleStateException(ExceptionConverterImpl.java:212) at org.hibernate.internal.ExceptionConverterImpl.convert(ExceptionConverterImpl.java:86) at org.hibernate.internal.ExceptionConverterImpl.convert(ExceptionConverterImpl.java:155) at org.hibernate.internal.ExceptionConverterImpl.convert(ExceptionConverterImpl.java:162) at org.hibernate.internal.SessionImpl.doFlush(SessionImpl.java:1434) at org.hibernate.internal.SessionImpl.flush(SessionImpl.java:1414)
But when I enable show_sql to see the scripts, the issue does not occur. Also, when I delay the persist with 10 milliseconds, I could not reproduce the issue:
Thread.sleep(10);
saveOrUpdate(userBo.getAddressSet());
This leads me to believe that the issue is not with multiple transactions modifying the same data, but rather multiple calls to the same table within the transaction.
Questions:
What is the best approach to handle this scenario? I believe it should involve retrying the mechanism. In that case, what is the best approach? Can we retry within the same transaction (first approach) or from a new transaction (meaning retry the operation from the beginning)?
Any help would be appreciated.
Retrying business transactions as a generic recovery strategy for the OptimisticLockException is only feasible for simplistic cases.Sophisticated use cases usually require merging so User Interface adjustments