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



我有一个在web服务器上运行的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):是更新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 

通常不推荐使用悲观锁定,因为它在数据库端的性能方面代价非常高。您提到的问题(代码部分)有几件事不清楚,如:

  • 如果你的代码被多个线程同时访问。
  • 如何创建session对象(不确定是否使用Spring)?

Hibernate会话对象不是线程安全的。因此,如果有多个线程访问同一个会话并尝试更新同一个数据库实体,您的代码可能会以这样的错误情况告终。

所以这里发生的事情是,不止一个线程尝试更新同一个实体,一个线程成功,当下一个线程去提交数据时,它看到它已经被修改了,并最终抛出StaleObjectStateException

编辑:

有一种方法可以在Hibernate中使用悲观锁定。点击这个链接。但这种机制似乎存在一些问题。然而,我在hibernate中遇到了一个bug (hh -5275)。bug中提到的场景如下:

两个线程正在读取同一个数据库记录;其中一个线程应该使用悲观锁定,从而阻塞其他线程。但两个线程都可以读取数据库记录,导致测试失败。

这与你所面对的非常接近。请尝试这个,如果这不起作用,我能想到的唯一方法是使用本地SQL查询,在那里你可以实现悲观锁定在postgres数据库与SELECT FOR UPDATE查询。

我们有一个队列管理器,它轮询数据并将其提供给处理程序进行处理。为了避免再次拾取相同的事件,队列管理器以LOCKED状态锁定数据库中的记录。

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

这个方法不是事务性的,但是dao.getLockedEntity()REQUIRED是事务性的。

一切顺利,在生产几个月后,它失败了,出现了一个乐观锁定异常。

经过大量的调试和检查细节后,我们发现有人这样修改了代码:

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

因此,记录甚至在dao.getLockedEntity()中的事务提交之前排队(它使用相同的轮询方法事务),并且在poll()方法事务提交时,对象被处理程序(不同的线程)在下面更改。

我们解决了这个问题,现在看起来很好。我想到了共享它,因为乐观锁异常可能令人困惑,并且难以调试。

看起来您实际上并没有使用从数据库检索的电子邮件,而是使用作为参数的旧副本。在检索前一个版本和进行更新之间,行上用于版本控制的任何内容都发生了变化。

你可能希望你的代码看起来更像:

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

我在项目中遇到了这个问题。

在我实现乐观锁之后,我得到了相同的异常。我的错误是我没有删除成为@Version的字段的setter。当在java空间中调用setter时,字段的值不再与DB生成的值匹配。基本上版本字段不再匹配了。此时,对实体的任何修改都会导致:

org.hibernate。StaleObjectStateException:行被更新或删除另一个事务(或未保存值映射不正确)

我在内存DB和Hibernate中使用H2

此异常可能是由乐观锁定(或代码中的错误)引起的。你可能在不知情的情况下使用了它。你的伪代码(应该被真实的代码取代,以便能够诊断问题)是错误的。Hibernate自动保存对附加实体所做的所有修改。你不应该在附加实体上调用update, merge或saveOrUpdate。只做

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

不需要调用update。Hibernate将在提交事务之前自动刷新更改。

我在多个Spring项目中遇到相同错误的问题。对我来说,一般的解决方案是,拆分我的服务方法每个INSERT, UPDATE和DELETE操作都有一个自己的方法与@Transactional。我认为这个问题与内部Spring管理有关,其中数据库交互是在方法结束时执行的,在我看来,这就是触发Exception的地方。

更新和进一步的解决方案。

我的问题是,我查询了一个@Entity Class对象并更改了一个值,而没有保存它,因为严格来说,它是由另一个查询(在作用域之外)更新的,但是由于这个对象是映射中会话的内部,现在它有一个不同的值,下一个请求被这个消息阻塞。

因此,我创建了一个变量并将新值保存在那里,然后将它们发送给UpdateQuery,因此Hibernate不会注册任何未保存的更改,并且可以更新该行。Hibernate似乎在每次更改@Entity类的对象时都会向数据库发送一个锁语句,或者至少通过主键在本地插入一行。

我有同样的问题,在我的情况下,问题是缺失和/或不正确的等于在实体对象中的某些类型的字段实现。在提交时,Hibernate检查会话中加载的所有实体,以检查它们是否脏。如果任何实体是脏的,hibernate会尝试持久化它们——不管保存操作请求的实际对象与其他实体无关。

实体脏污是通过比较给定对象的每个属性(与它们的equals方法)或UserType.equals(如果属性有关联的org.Hibernate.UserType)来完成的。

另一件令我惊讶的事情是,在我的事务(使用Spring注释@Transactional)中,我处理的是单个实体。Hibernate抱怨一些与被保存的实体无关的随机实体。我意识到的是,我们在REST控制器级别创建了一个最外层的事务,因此会话的范围太大,因此作为请求处理的一部分加载的所有对象都会被检查是否脏。

希望有一天这能帮助到别人。

Thanks Rags

以防有人检查了这个线程并遇到了与我相同的问题…

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

我正在使用NHibernate,我收到了相同的错误,在创建对象期间…

我正在手动传递密钥,并且还在映射中指定了GUID生成器…

和hibernate生成相同的错误,因此,一旦我删除了GUID,并将字段保留为空,一切都很好。

这个答案可能不会帮助你,但会帮助像我这样的人,谁只是查看你的线程,因为同样的错误

检查对象是否存在在DB中,如果它存在得到对象并刷新它:

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

如果不满足上述if条件…在DB中找到Id为Object的对象,做相应的操作,在这种情况下会反映出准确的变化。

if (....) {
    } else if (null != identity) {
        E dbInstance = (E) getEntityManager().find(instance.getClass(), identity);
        return dbInstance;
    }

我在不同的项目背景下经历过同样的问题,有不同的场景,如

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

第一种情况

当我发出一个服务器调用时,在保存那个对象之前它们一次调用来自js并试图保存另一个地方,我看到js调用进行了两到三次(我认为是调用绑定导致了这个问题)

通过

求解
e.preventDefault()

第二个大小写

object.lock()

我也收到了这样的异常,但问题是在我的实体标识符。我正在使用UUID,并且在Spring与它们一起工作的方式中存在一些问题。所以我只是把这行添加到我的实体标识符中,它开始工作了:

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

在这里你可以找到更多的信息

当我试图从2个不同的会话更新同一行时,发生了这个错误。我在一个浏览器中更新了一个字段,而另一个浏览器是打开的,并且已经将原始对象存储在其会话中。当我尝试从第二个"陈旧"会话更新时,我得到了陈旧对象错误。为了纠正这一点,在设置要更新的值之前,我从数据库中重新获取要更新的对象,然后将其保存为正常状态。

在创建新行后尝试更新现有行时,我也遇到了这个错误,并且花了很长时间挠头,挖掘事务和版本逻辑,直到我意识到我为我的一个主键列使用了错误的类型。

当我应该使用LocalDateTime时,我使用了LocalDate -我认为这导致hibernate无法区分实体,导致此错误。

将密钥更改为LocalDateTime后,错误消失。此外,更新单独的行也开始工作了——以前它无法找到要更新的行,测试这个单独的问题实际上使我得出了关于主键映射的结论。

我有同样的问题,对我来说,情况有点不同,我使用Spring Data JPA和实体类用@Entity和@Table注释进行注释,在ID字段上我有@Id注释,但我错过了添加@GeneratedValue,因为DB表具有自动增量标识字段。

但是当我们对这些实体进行批量插入时,由于没有在ID字段上指定Generator,所以所有实体的ID字段都是默认值(0)。并开始给出这个异常:

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

我们添加了@GeneratedValue(strategy = GenerationType.IDENTITY)和@Id,它工作了。

我在我的grails项目中遇到了同样的问题。问题是,我重写了集合字段的getter方法。这总是返回其他线程中集合的新版本。

class Entity {
    List collection
    List getCollection() {
        return collection.unique()
    }
}
解决方法是重命名getter方法:
class Entity {
    List collection
    List getUniqueCollection() {
        return collection.unique()
    }
}

不要将Id设置为您正在保存的对象,因为Id将自动生成

如果您正在使用Hibernate和Dropwizard,如果您使用id作为自动生成,则可能发生这种情况。删除@GeneratedValue

在这里输入图像描述

Hibernate使用版本控制来知道您拥有的修改对象比当前持久化的对象更早。

所以当你更新一个实体时,如果它不需要,不要在json body中包含version。只需在版本列中注释@Version。

错误原因

有另一种情况:数据错误。

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

其中一个数据为空或null。保存前端值时,

{
    "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

删除错误数据

如果你试图更新一个对象,这是相同的实例,但从不同的列表/哈希/等检索,从不同的子线程发生这个问题。

以防它对同样情况下的其他人有所帮助:当我试图删除一个对象(通过session.delete)时,我得到了这个异常,并且忘记了该对象也是通过delete-orphan从集合中删除的。因此,当我的delete()调用出现时,对象已经通过级联从集合中删除了。

在我的情况下,我首先在主线程中删除一个实体,然后创建一个新实体,通过调用外部服务填充它,并将其保存在一个单独的线程中(通过使用CompletableFuture)。我不得不将删除移到CompletableFuture逻辑部分。

为了防止StaleObjectStateException,在您的hbm文件中写入以下代码:

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

首先检查您的导入,当您使用会话时,事务应该是org.hibernate并删除@Transactinal注释。最重要的是,在实体类中,如果你使用@GeneratedValue(strategy=GenerationType.AUTO)或任何其他,那么在模型对象创建/实体对象创建时不应该创建id。最后的结论是,如果你想通过id字段,即PK,那么从实体类中删除@GeneratedValue

我有这个问题在我的一个应用程序,现在,我知道这是一个旧线程,但这里是我的解决方案;我通过查看调试器内部的数据发现,当Hibernate试图更新数据库时(实际上是在不同的线程中完成的),JVM实际上没有正确加载它,因此我在实体的每个字段中添加了关键字"volatile"。这样做会有一些性能问题但是比起重物被扔来扔去…

最新更新