94

I have a java project that runs on a webserver. I always hit this exception.

I read some documentation and found that pessimistic locking (or optimistic, but I read that pessimistic is better) is the best way to prevent this exception.

But I couldn't find any clear example that explains how to use it.

My method is like:

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

while:

  • Email is a Hibernate class (it will be a table in the database)
  • getEmailById(String id) is a function that returns an email (this method is not annotated with @Transactional)
  • updateEmail(email): is a method that updates the email.

Note: I use Hibernate for save, update & so on (example: session.getcurrentSession.save(email))

The exception:

ERROR 2011-12-21 15:29:24,910 Could not synchronize database state with session [myScheduler-1]
org.hibernate.StaleObjectStateException: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect): [email#21]
    at org.hibernate.persister.entity.AbstractEntityPersister.check(AbstractEntityPersister.java:1792)
    at org.hibernate.persister.entity.AbstractEntityPersister.update(AbstractEntityPersister.java:2435)
    at org.hibernate.persister.entity.AbstractEntityPersister.updateOrInsert(AbstractEntityPersister.java:2335)
    at org.hibernate.persister.entity.AbstractEntityPersister.update(AbstractEntityPersister.java:2635)
    at org.hibernate.action.EntityUpdateAction.execute(EntityUpdateAction.java:115)
    at org.hibernate.engine.ActionQueue.execute(ActionQueue.java:279)
    at org.hibernate.engine.ActionQueue.executeActions(ActionQueue.java:263)
    at org.hibernate.engine.ActionQueue.executeActions(ActionQueue.java:168)
    at org.hibernate.event.def.AbstractFlushingEventListener.performExecutions(AbstractFlushingEventListener.java:321)
    at org.hibernate.event.def.DefaultFlushEventListener.onFlush(DefaultFlushEventListener.java:50)
    at org.hibernate.impl.SessionImpl.flush(SessionImpl.java:1027)
    at org.hibernate.impl.SessionImpl.managedFlush(SessionImpl.java:365)
    at org.hibernate.transaction.JDBCTransaction.commit(JDBCTransaction.java:137)
    at org.springframework.orm.hibernate3.HibernateTransactionManager.doCommit(HibernateTransactionManager.java:656)
    at org.springframework.transaction.support.AbstractPlatformTransactionManager.processCommit(AbstractPlatformTransactionManager.java:754)
    at org.springframework.transaction.support.AbstractPlatformTransactionManager.commit(AbstractPlatformTransactionManager.java:723)
    at org.springframework.transaction.interceptor.TransactionAspectSupport.commitTransactionAfterReturning(TransactionAspectSupport.java:393)
    at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:120)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172)
    at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:202)
    at $Proxy130.generateEmail(Unknown Source)
    at com.admtel.appserver.tasks.EmailSender.run(EmailNotificationSender.java:33)
    at com.admtel.appserver.tasks.EmailSender$$FastClassByCGLIB$$ea0d4fc2.invoke(<generated>)
    at net.sf.cglib.proxy.MethodProxy.invoke(MethodProxy.java:149)
    at org.springframework.aop.framework.Cglib2AopProxy$CglibMethodInvocation.invokeJoinpoint(Cglib2AopProxy.java:688)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:150)
    at org.springframework.aop.aspectj.AspectJAfterThrowingAdvice.invoke(AspectJAfterThrowingAdvice.java:55)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:161)
    at org.springframework.aop.framework.adapter.AfterReturningAdviceInterceptor.invoke(AfterReturningAdviceInterceptor.java:50)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:161)
    at org.springframework.aop.framework.adapter.MethodBeforeAdviceInterceptor.invoke(MethodBeforeAdviceInterceptor.java:50)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:161)
    at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:89)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172)
    at org.springframework.aop.framework.Cglib2AopProxy$DynamicAdvisedInterceptor.intercept(Cglib2AopProxy.java:621)
    at com.admtel.appserver.tasks.EmailNotificationSender$$EnhancerByCGLIB$$33eb7303.run(<generated>)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
    at java.lang.reflect.Method.invoke(Method.java:597)
    at org.springframework.util.MethodInvoker.invoke(MethodInvoker.java:273)
    at org.springframework.scheduling.support.MethodInvokingRunnable.run(MethodInvokingRunnable.java:65)
    at org.springframework.scheduling.support.DelegatingErrorHandlingRunnable.run(DelegatingErrorHandlingRunnable.java:51)
    at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:441)
    at java.util.concurrent.FutureTask$Sync.innerRunAndReset(FutureTask.java:317)
    at java.util.concurrent.FutureTask.runAndReset(FutureTask.java:150)
    at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.access$101(ScheduledThreadPoolExecutor.java:98)
    at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.runPeriodic(ScheduledThreadPoolExecutor.java:180)
    at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:204)
    at java.util.concurrent.ThreadPoolExecutor$Worker.runTask(ThreadPoolExecutor.java:886)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:908)
    at java.lang.Thread.run(Thread.java:680)
ERROR 2011-12-21 15:29:24,915 [ exception thrown < EmailNotificationSender.run() > exception message Object of class [Email] with identifier [211]: optimistic locking failed; nested exception is org.hibernate.StaleObjectStateException: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect): [Email#21] with params ] [myScheduler-1]
org.springframework.orm.hibernate3.HibernateOptimisticLockingFailureException: Object of class [Email] with identifier [21]: optimistic locking failed; nested exception is 
Dmitriy Popov
  • 2,150
  • 3
  • 25
  • 34
Georgian Citizen
  • 3,727
  • 6
  • 38
  • 46
  • 1
    Are you sure that the database is not being updated from multiple places and you code is the only one doing it ? Also is your method `test(Email email, String Subject)` being accessed concurrently ? – Santosh Dec 27 '11 at 14:34
  • i use postgre database. no my project still under testing only in my pc so no one can access it, i am only who update data on the database, iam create a service that's call this method automatically every 30 seconds, this service is the unique service that call this method – Georgian Citizen Dec 28 '11 at 07:05

25 Answers25

71

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 threads try 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.

Santosh
  • 17,667
  • 4
  • 54
  • 79
  • i know that, that's why i need to lock the database until current session finish its update. how can i lock the database(optimistic/pessimistic)? please till me by code if you can. iam using spring but this is a small example from my project. thanks – Georgian Citizen Dec 28 '11 at 06:55
  • Since your solution came high up in a search list results, I felt like asking you what you mean with: one thread succeeds. Does that thread commit the entity or not ? – Stephane Sep 30 '15 at 14:56
  • Yes. Succeeds means the thread was able to complete the transaction (or commit the entity). – Santosh Oct 01 '15 at 01:38
  • By 'entity' did you mean the same row in database and not the table. Right? – Aseem Goyal Mar 29 '17 at 09:43
  • Yes. I meant the same. – Santosh Mar 30 '17 at 04:16
  • I'm just trying to update the dang db, not understand my insurance policy... Could you re-explain this in english / more simple terms? Pessimistic locking? Thread safe? A brief explanation would be great... *gives me link to hibernate documentation* – Collin Jan 30 '23 at 20:59
  • @Collin, please check this https://docs.jboss.org/hibernate/stable/core.old/reference/en/html/transactions.html – Santosh Feb 02 '23 at 15:54
  • @Santosh Jboss documentation, I'll take it! So a Session is not thread safe because you could have multiple threads, each of which have their own Session defined, but they don't "update" each other's sessions when one updates something, which can cause conflicts especially when doing operations on the same table at the same time by different threads. (?) – Collin Feb 02 '23 at 19:27
  • Yes. That is correct. – Santosh Feb 03 '23 at 13:21
20

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.

Kalle Richter
  • 8,008
  • 26
  • 77
  • 177
Lyju I Edwinson
  • 1,764
  • 20
  • 20
  • 1
    what do you mean by 'Required' next to '@Transactional'? – tk_ Mar 01 '18 at 02:51
  • 3
    it is the propagation, I just didn't type it all there, it is actually @Transactional(propagation = Propagation.REQUIRED) Please check https://docs.spring.io/spring/docs/4.2.x/spring-framework-reference/html/transaction.html – Lyju I Edwinson Mar 01 '18 at 09:32
  • 6
    How did you fix the issue? Was it by adding @Transactional(propagation=Propagation.REQUIRED, readOnly=false) or removing it? I did not quite understand that, apologies. – coretechie Jun 21 '18 at 09:16
  • 1
    By removing the @Transactional on the poll method and this will allow the Transaction on dao.getLockedEntity() to be completed before the queue method is invoked. Hence the objects can now be modified by the task handlers when it is queued using the queue method since the transaction is already finished and the objects are detached from the hibernate session created by dao.getLockedEntity(). – Lyju I Edwinson Jan 14 '20 at 01:08
17

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);
    }
Dmitriy Popov
  • 2,150
  • 3
  • 25
  • 34
tvanfosson
  • 524,688
  • 99
  • 697
  • 795
  • 1
    for sure the version is the problem. but this change in the method didn't affect i try this solution more than one time. if you have another solution please post it herethanks – Georgian Citizen Dec 28 '11 at 06:58
11

I had the this problem on my project.

After I implemented optimistic locking, I got the same exception. My mistake was that I did not remove the setter of the field that became the @Version. As the setter was being called in java space, the value of the field did not match the one generated by the DB anymore. So basically the version fields did not match anymore. At that point any modification on the entity resulted in:

org.hibernate.StaleObjectStateException: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect)

I am using H2 in memory DB and Hibernate.

Tom11
  • 2,419
  • 8
  • 30
  • 56
MF Wolf
  • 111
  • 1
  • 3
6

This exception is probably caused by optimistic locking (or by a bug in your code). You're probably using it without knowing. And your pseudo-code (which should be replaced by real code to be able to diagnose the problem) is wrong. Hibernate saves all the modifications done to attached entities automatically. You shouldn't ever call update, merge or saveOrUpdate on an attached entity. Just do

Email email = session.get(emailId);
email.setSubject(subject);

No need to call update. Hibernate will flush the changes automatically before committing the transaction.

peterh
  • 11,875
  • 18
  • 85
  • 108
JB Nizet
  • 678,734
  • 91
  • 1,224
  • 1,255
  • this is not the main problem. update function not cause this error. i want to use optimistic or pessimistic locking in my project how can i use them as my scenario? thanks – Georgian Citizen Dec 28 '11 at 07:01
  • 1
    optimistic locking consists in detecting that another transaction has updated/deleted the same row, and throwing the exception you're getting in this case. If you have a version field, you're already using optimistic locking, and this is what throws the exception. Pessimistic locking will make your application slow and subject to deadlocks. Deal with it: if several users are updating the same rows, one of them will get exceptions. It's normal and expected. – JB Nizet Dec 28 '11 at 07:10
  • it is not normal if several users updating the same row and one get exception, because users in other functions updating balance. if one user get an exception then balance not updated. now i am only who access this program an i get this exception without several users access it, what will happen if many users access it. do you know how can i use pessimistic locking – Georgian Citizen Dec 28 '11 at 08:04
  • 4
    If you're the only one accesing the database and you get this exception, then you have a problem in your code. Show us your code. Having exceptions is expected and normal if you have several concurrent users on the same row. Catch the exception, and inform the user that his operation didn't succeed, that he should refresh and try again. – JB Nizet Dec 28 '11 at 08:11
4

I had problems with the same error on more than one Spring project. For me a general solution was, to split my service Method, that each INSERT, UPDATE and DELETE action got an own Method with @Transactional. I think this problem relates to the internal Spring managment, where database interactions are executed at the end of the method and, in my oppinion, this is the point, where the Exception is triggered.

Update and further solutions.

My problem was that I queried an @Entity Class object and changed a value without saving it because, strictly speaking, it was updated by another query (outside the scope), but since this object was internal to the sessions in a map now it had a different value, the next request was blocked with this message.

So I created a variable and saved the new values there and then sent them to the UpdateQuery, so Hibernate did not register any unsaved changes and the line could be updated. Hibernate seems to send a lock statement to the database every time an object of the @Entity class is changed or at least to spear the line locally by primary key.

BeatEngine
  • 123
  • 7
3

Just in case someone checked this thread and had the same issue as mine...

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

I'm using NHibernate, I received same error, during creation of an object...

I was passing the key manually, and also specified a GUID generator in mapping...

And hibernate generate same exact error for me, So once I removed the GUID, and left the field empty, everything went just fine.

This answer may not help you, but will help someone like me, who just viewed your thread because of same error

Hassan Faghihi
  • 1,888
  • 1
  • 37
  • 55
3

I had the same problem and in my case the problem was missing and/or incorrect equals implementation on some types of fields in the entity object. At commit time, Hibernate checks ALL entities loaded in the session to check if they are dirty. If any of the entities are dirty, hibernate tries to persist them - no matter of the fact that the actual object that is requested a save operation is not related to the other entities.

Entity dirtiness is done by comparing every property of given object (with their equals methods) or UserType.equals if property has an associated org.Hibernate.UserType.

Another thing that surprised me was, in my transaction (using Spring annotation @Transactional), I was dealing with a single entity. Hibernate was complaining about some random entity that's unrelated to that entity being saved. What I realized is there is an outermost transaction we create at REST controller level, so the scope of the session is too big and hence all objects ever loaded as part of request processing get checked for dirtiness.

Hope this helps someone, some day.

Thanks Rags

Tom11
  • 2,419
  • 8
  • 30
  • 56
2

check if the object exists or not in DB, if it exists get the object and refresh it:

if (getEntityManager().contains(instance)) {
    getEntityManager().refresh(instance);
    return instance;
}

if it fails the above if condition... find the Object with Id in DB, do the operation which you need, in this case exactly changes will reflects.

if (....) {
    } else if (null != identity) {
        E dbInstance = (E) getEntityManager().find(instance.getClass(), identity);
        return dbInstance;
    }
Daniel Serodio
  • 4,229
  • 5
  • 37
  • 33
pavan
  • 33
  • 2
2

I had the experienced the same issue in different context of my project and there are different scenarios like

 - object is accessed from various source like (server side and client)
 - without any interval accessing the same object from a different place

In the first case

When I issue a server cal, before save the that object their one call from js and trying to save and another place, I got like, js call is going two, three times(I thing that call binding thing cause the issue)

I solved by

e.preventDefault()

The second case,

object.lock()
2

I was also receiving such an exception, but the problem was in my Entity identifier. I am using UUID and there are some problems in the way Spring works with them. So I just added this line to my entity identifier and it began working:

@Column(columnDefinition = "BINARY(16)")

Here you can find a little bit more information.

Kamo Spertsian
  • 785
  • 2
  • 8
  • 23
  • Thank you for this very useful tip: it is exactly the solution to a problem that was driving me crazy for hours :) – Francesco Galgani Aug 07 '20 at 12:17
  • Are you adding this one on the `primary Key` of the table ? I am getting the below error `org.hibernate.StaleObjectStateException: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect)` – Ravindra Kushwaha May 08 '23 at 09:24
1

This error occurred for me when I was trying to update the same row from 2 different sessions. I updated a field in one browser while a second was open and had already stored the original object in its session. When I attempted to update from this second "stale" session I get the stale object error. In order to correct this I refetch my object to be updated from the database before I set the value to be updated, then save it as normal.

Roger
  • 10,851
  • 3
  • 26
  • 39
1

I also ran into this error when attempting to update an existing row after creating a new one, and spent ages scratching my head, digging through transaction and version logic, until I realised that I had used the wrong type for one of my primary key columns.

I used LocalDate when I should have been using LocalDateTime – I think this was causing hibernate to not be able to distinguish entities, leading to this error.

After changing the key to be a LocalDateTime, the error went away. Also, updating individual rows began to work as well – previously it would fail to find a row for updating, and testing this separate issue was actually what led me to my conclusions regarding the primary key mapping.

Josh Manderson
  • 328
  • 1
  • 13
1

I had the same issue and for me, the case was a bit different, I was using Spring Data JPA and the entity class was annotated with @Entity and @Table annotation, and on the ID field I had @Id annotation but I missed adding @GeneratedValue since the DB table had the auto-increment identity field.

But the issue happened when we were doing bulk insert for these entities and since there was no Generator specified on the ID field, all entities had the default value (0) as the id field. and Started giving this exception:

javax.persistence.OptimisticLockException: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect) :[dao.entity.OrderAssortmentReportEntity#0]

We added the @GeneratedValue(strategy = GenerationType.IDENTITY) along with @Id and it worked.

balram rajput
  • 55
  • 2
  • 8
0

I had the same problem in my grails project. The Bug was, that i overwrite the getter method of a collection field. This returned always a new version of the collection in other thread.

class Entity {
    List collection

    List getCollection() {
        return collection.unique()
    }
}

The solution was to rename the getter method:

class Entity {
    List collection

    List getUniqueCollection() {
        return collection.unique()
    }
}
moskauerst
  • 141
  • 1
  • 3
0

Don't set an Id to the object you are saving as the Id will be autogenerated

w0ns88
  • 344
  • 2
  • 9
  • 28
0

if you are using Hibernate with Dropwizard, this could happen if you are using id as autogenerated. Remove @GeneratedValue

enter image description here

Community
  • 1
  • 1
0

Hibernate uses versioning to know that modified object you had is older than one which is currently persisted.

so when you update an entity don't include version in json body if its unwanted. just annotate with @Version in version column.

Nelcon Croos
  • 71
  • 1
  • 5
0

1. Reason for error

There is another situation: Error data.

@Column(name = "ID", unique = true, nullable = false, length = 32)
private String id;

One of the data is blank or null. When the front-end value is saved,

{
    "cause": {
        "cause": null,
        "message": "Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect) : [com.xxx#]"
    },
    "message": "Object of class [com.xxx] with identifier []: optimistic locking failed; nested exception is org.hibernate.StaleObjectStateException: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect) : [com.xxx#]"
}

2 .solving

Delete error data.

Z.Rengui
  • 49
  • 4
0

This problem happens if you are trying to update an object which is the same instance but retrieved from different List/Hash/ and so on, right from a different sub-thread.

cigien
  • 57,834
  • 11
  • 73
  • 112
0

In case it helps someone else in the same situation: I got this exception when trying to delete an object (via session.delete) and had forgotten that the object was also removed from a collection with delete-orphan. So when my delete() call came up, the object had already been deleted by cascade from the collection.

Zaleth
  • 1
  • 2
0

In my case I was first deleting an entity in a main thread and then creating a new entity, populating it by calling external services and saving it in a separate thread (by using CompletableFuture). I had to move deletion to the CompletableFuture piece of logic.

Vitaly Chura
  • 704
  • 8
  • 13
-1

In order to prevent StaleObjectStateException, in your hbm file write below code:

<timestamp name="lstUpdTstamp" column="LST_UPD_TSTAMP" source="db"/>
Tom11
  • 2,419
  • 8
  • 30
  • 56
Yusuf
  • 1
  • @Yusuf - Tip: To ensure XML code is visible, use the code button `{}` in the toolbar or indent the line four(4) spaces. – Leigh Oct 31 '15 at 01:21
  • 2
    You should explain what your code is doing and how it fixes the question. I don't think this is a solution though. – Stealth Rabbi Jan 19 '17 at 14:45
-1

First check your imports, when you use session, transaction it should be org.hibernate and remove @Transactinal annotation. and most important in Entity class if you have used @GeneratedValue(strategy=GenerationType.AUTO) or any other then at the time of model object creation/entity object creation should not create id. final conclusion is if you want pass id filed i.e PK then remove @GeneratedValue from entity class.

Dharman
  • 30,962
  • 25
  • 85
  • 135
shubham bellale
  • 201
  • 2
  • 5
-3

I had this problem in one of my apps, now, I know this is an old thread but here is my solution; I figured out by looking at the data inside the debugger that JVM actually didn't load it properly when Hibernate was trying to update the database (that is actually done in a different thread), so I added the keyword "volatile" to every field of the entities. It has some performance issues to do that but rather that than Heavy objects beeing thrown around...

Chiana
  • 11