Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect)

Pessimistic locking is generally not recommended and it's very costly in terms of performance on database side. The problem that you have mentioned (the code part) a few things are not clear such as:

  • If your code is being accessed by multiple threads at the same time.
  • How are you creating session object (not sure if you are using Spring)?

Hibernate Session objects are NOT thread-safe. So if there are multiple threads accessing the same session and trying to update the same database entity, your code can potentially end up in an error situation like this.

So what happens here is that more than one thread tries to update the same entity, one thread succeeds and when the next thread goes to commit the data, it sees that its already been modified and ends up throwing StaleObjectStateException.

EDIT:

There is a way to use Pessimistic Locking in Hibernate. Check out this link. But there seems to be some issue with this mechanism. I came across posting a bug in hibernate (HHH-5275), however. The scenario mentioned in the bug is as follows:

Two threads are reading the same database record; one of those threads should use pessimistic locking thereby blocking the other thread. But both threads can read the database record causing the test to fail.

This is very close to what you are facing. Please try this if this does not work, the only way I can think of is using Native SQL queries where you can achieve pessimistic locking in postgres database with SELECT FOR UPDATE query.


I know this is an old question, but some of us are still hitting it and look at the sky wondering how. Here is one kind of issue that I faced.

We have a queue manager that polls data and gives it to handlers for processing. To avoid picking up the same events again, the queue manager locks the record in the database with a LOCKED state.

    void poll() {
        record = dao.getLockedEntity();
        queue(record);
    }

this method wasn't transactional but dao.getLockedEntity() was transactional with REQUIRED.

All good and on the road, after few months in production, it failed with an optimistic locking exception.

After lots of debugging and checking in details we could find out that some one has changed the code like this:

    @Transactional(propagation=Propagation.REQUIRED, readOnly=false)
    void poll() {
        record = dao.getLockedEntity();
        queue(record);              
    }

So the record was queued even before the transaction in dao.getLockedEntity() gets committed (it uses the same transaction of poll method) and the object was changed underneath by the handlers (different threads) by the time the poll() method transaction gets committed.

We fixed the issue and it looks good now.

I thought of sharing it because optimistic lock exceptions can be confusing and are difficult to debug. Someone might get benefited from my experience.

Regards, Lyju


It doesn't appear that you are actually using the email that you retrieve from the database, but an older copy that you get as a parameter. Whatever is being used for version control on the row has changed between when the previous version was retrieved and when you are doing the update.

You probably want your code to look more like:

    @Transactional
    public void test(String id, String subject) {
       Email email = getEmailById(id);
       email.setSubject(subject);
       updateEmail(email);
    }