Hi All,
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:
xmlCopy code
<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">
</version>
<set name="address" inverse="true" lazy="false" cascade="delete">
<key column="ADDRESS_SET_ID" />
<one-to-many class="AddressDO"/>
</set>
</class>
Address.hbm.xml:
xmlCopy code
<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
javaCopy code
// 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):
javaCopy code
saveOrUpdate(userBo);
if(userBo.getAddressSet() != null)
saveOrUpdate(userBo.getAddressSet());
saveOrUpdate(userBo.getAddressSet().getAddress());
And my saveOrUpdate code is:
javaCopy code
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())
:
cssCopy code
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:
javaCopy code
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)?
First approach: Retry within the transaction:
javaCopy code
void saveOrUpdate(obj) {
try {
hibernateSession().saveOrUpdate(obj);
checkReadOnlySession();
hibernateSession().flush();
} catch (PersistenceException e) {
// Apply retry mechanism
hibernateSession().refresh(obj);
hibernateSession().saveOrUpdate(obj);
checkReadOnlySession();
hibernateSession().flush();
}
}
Any help would be appreciated.