行被另一个事务更新或删除(或未保存的值映射不正确)

我有一个 java 项目,运行在一个网络服务器。我总是打这个异常。

我阅读了一些文档,发现悲观锁定(或者乐观锁定,但我读到悲观锁定更好)是防止此异常的最佳方法。

但我找不到任何明确的例子来解释如何使用它。

我的方法是这样的:

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

同时:

  • Email是一个 Hibernate 类(它将是数据库中的一个表)
  • getEmailById(String id)是一个返回 email的函数(此方法没有使用 @Transactional进行注释)
  • updateEmail(email): 是一种更新电子邮件的方法。

注意: 我使用 Hibernate 进行保存、更新等操作(例如: session.getcurrentSession.save(email))

例外:

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
307720 次浏览

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);
}

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.

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.

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;
}

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.

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()

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.

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

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.

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

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

<timestamp name="lstUpdTstamp" column="LST_UPD_TSTAMP" source="db"/>

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

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()
}
}

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.

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.

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.

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

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.

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

enter image description here

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.

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.

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.

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.