Handling javax.persistence.OptimisticLockException in a legacy Spring Hibernate application

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 encounterOptimisticLockException` 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:

  1. What is the best approach to handle this scenario?
  2. 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.

I would suggest you to try and create a reproducer with our test case template so that someone can actually debug into this. If you are able to reproduce the issue and confident that this is a bug, create a bug ticket in our issue tracker and attach that reproducer.