How should one handle javax.persistence.OptimisticLockException in a legacy Spring Hibernate application

36 views Asked by At

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.

1

There are 1 answers

0
unknown On

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