Issues

Select view

Select search mode

 

Batched unidirectional one-to-many update failing

Description

For a unidirectional one-to-many relation moving a "many" entity from a "one" entities' collection to another "one" can fail with a StaleStateException in batched mode if the SQL statements happen to be executed in the "wrong” order.

Working example

Considering this mapping (simplified excerpt):

Running this code to move phone from person1's phones collection to person2 usually works:

It executes these SQL statements both with and without batching and everything works fine:
update Phone set person_id=null where person_id=? and id=? with ? bound to 1 and 1
update Phone set person_id=? where id=? with ? bound to 2 and 1

Breaking example

However, if we re-order loading the Person entities

The SQL statement order changes to:
update Phone set person_id=? where id=? with ? bound to 2 and 1
update Phone set person_id=null where person_id=? and id=? with ? bound to 1 and 1

This still works fine in non-batched mode. This mode ignores the number of actually affected statements, which is 0 for the second statement (as the old person_id column value has already been replaced by the first statement).

In batched mode we get an exception for the second statement instead:

Batch update returned unexpected row count from update [0]; actual row count: 0; expected: 1; statement executed: update Phone set person_id=null where person_id=? and id=?

jakarta.persistence.OptimisticLockException: Batch update returned unexpected row count from update [0]; actual row count: 0; expected: 1; statement executed: update Phone set person_id=null where person_id=? and id=?
at [...]
Caused by: org.hibernate.StaleStateException: Batch update returned unexpected row count from update [0]; actual row count: 0; expected: 1; statement executed: update Phone set person_id=null where person_id=? and id=?
at [...]

For the SQL statements to look like this (and to fail), person1.getPhones() needs to contain another Phone that stays there. See the reproduce for the full code.

Reproducer

Analysis
One reason for this is that the two Persons' phones collections are flushed independently of each other despite both referring to the same database row (for the one phone). Depending on the execution order the update Phone set person_id=null ... statement does not change anything,
It seems changing the independent flushing approach is very hard considering the current implementation. To me it looks easier to make Hibernate ignore that in some cases statements have fewer affected rows than usual.

The flush order of the two collections is determined by the order the Person entities are loaded into the session (or by their ids when using hibernate.order_updates).

The exception happens in Hibernate 6.6 (and at least 6.4), but no longer in 7.0/main. The behavior changed as a side-product of , which now swallows the StaleStateException in this case.
I'm not sure this behavior change was intentional and can serve as the basis for a fix in 6.6. I will add a comment in that ticket to discuss this.
If it was not intentional, it can make sense to use org.hibernate.jdbc.Expectation.None to ignore the number of affected rows for specific statements.

Non-batched mode is not affected as DeleteRowsCoordinatorStandard.deleteRows passes a null OperationResultChecker to the actual SQL execution, ignoring the number of affected rows.

As a workaround one can flush() between the remove() and the add() call in the example to prevent SQL reordering. But that is of course bad for performance.

Details

Assignee

Reporter

Components

Affects versions

Priority

Created last week
Updated 2 days ago

Activity

Yashwanth2 days ago

We are currently upgrading to Hibernate 6.6.5 where we are facing a similar issue with a delete operation that throws an OptimisticLockException with the following stacktrace:

We are unsure how the Hibernate release schedule works but in our organization we still use hbm.xml, which is deprecated but not removed in 6.6.5. If there is a fix for this issue, will there be a backport? Since we are still in our upgrading phase and cannot directly move to 7.0 (where hbm.xml is removed), any suggestions would be greatly appreciated.

Flag notifications