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

问题描述 投票:49回答:16

我有一个在Web服务器上运行的java项目。我总是遇到这个例外。

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

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

我的方法是这样的:

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

而:

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

注意:我使用hibernate进行保存,更新等等(例如:qazxsw poi)

例外:

session.getcurrentSession.save(email)
java hibernate spring amazon-web-services pessimistic-locking
16个回答
44
投票

通常不建议使用悲观锁定,并且在数据库方面的性能方面非常昂贵。您提到的问题(代码部分)有些事情并不清楚,例如:

  • 如果您的代码同时被多个线程访问。
  • 你是如何创建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 对象的(不确定你是否使用Spring)?

Hibernate session。因此,如果有多个线程访问同一个会话并尝试更新同一个数据库实体,那么您的代码可能最终会出现这样的错误情况。

所以这里发生的是多个线程尝试更新同一个实体,一个线程成功,当下一个线程提交数据时,它看到它已被修改并最终抛出Session objects are NOT thread-safe

编辑:

有一种方法可以在Hibernate中使用悲观锁定。看看StaleObjectStateException。但这种机制似乎存在一些问题。然而,我遇到了在hibernate(this link)中发布的错误。 bug中提到的场景如下:

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

这非常接近您面临的情况。请尝试这个,如果这不起作用,我能想到的唯一方法是使用HHH-5275,你可以用Native SQL queries查询实现pessimistic locking in postgres数据库。


1
投票

以防万一有人检查过这个帖子并且遇到与我相同的问题......

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

我正在使用NHibernate,在创建对象时收到同样的错误...

我手动传递了密钥,并且还在映射中指定了GUID生成器,因此hibernate为我生成了相同的错误,所以一旦我删除了GUID,并将字段留空,一切都很顺利。

这个答案可能对你没有帮助,但会帮助像我这样的人,因为同样的错误也是你的主线


1
投票

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

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

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


0
投票

我的grails项目中遇到了同样的问题。 Bug是,我覆盖了集合字段的getter方法。这在其他线程中始终返回该集合的新版本。

LocalDateTime

解决方案是重命名getter方法:

class Entity {
    List collection

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

0
投票

我也收到了这样的例外,但问题出在我的实体标识符中。我使用的是class Entity { List collection List getUniqueCollection() { return collection.unique() } } ,Spring在使用它们的方式上存在一些问题。所以我只是将这一行添加到我的实体标识符中,它开始工作:

UUID

@Column(columnDefinition = "BINARY(16)")你可以找到更多的信息。


-1
投票

为了防止Here,在你的StaleObjectStateException文件中写下代码:

hbm

-1
投票

我也遇到过这个问题。首先检查你的导入,当你使用session时,事务应该是org.hibernate并删除@Transactinal注释。如果你曾经使用过@GeneratedValue(strategy = GenerationType.AUTO)或其他任何一个,那么在模型对象创建/实体对象创建时,最重要的是在Entity类中不应该创建id。最后的结论是,如果你想要传递id,即PK然后从实体类中删除@GeneratedValue。


-3
投票

我在我的一个应用程序中遇到了这个问题,现在,我知道这是一个旧线程,但这是我的解决方案;我通过查看调试器内部的数据得出结论,当Hibernate试图更新数据库时(实际上是在另一个线程中完成),JVM实际上没有正确加载它,所以我在每个字段中添加了关键字“volatile”实体它有一些性能问题,而不是重物抛出...


13
投票

您实际上并不是在使用从数据库中检索的电子邮件,而是使用作为参数获取的旧版本。在检索以前版本和执行更新时,行上版本控制所使用的内容已经发生了变化。

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

SELECT FOR UPDATE

7
投票

我知道这是一个古老的问题,但是我们中的一些人仍在打它,看着天空如何徘徊。这是我遇到的一种问题,

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

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

这个方法不是事务性的,但 void poll() { record = dao.getLockedEntity(); queue(record); } 是'REQUIRED'的交易。

一切都很好,在路上,经过几个月的生产,它失败了乐观的锁定异常,

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

dao.getLockedEntity()

所以记录甚至在dao.getLockedEntity()中的事务被提交之前排队(它使用与poll方法相同的事务)并且当poll()方法事务获得时,处理程序(不同的线程)下面的对象被更改了comitted。

我们修复了这个问题,现在看起来不错。

我想分享它,因为乐观的锁异常可能令人困惑并且难以调试。有些人可能会从我的经验中受益。

关心Lyju


4
投票

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

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

无需致电更新。在提交事务之前,Hibernate将自动刷新更改。


3
投票

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

在我实现乐观锁定之后,我得到了同样的异常。我的错误是我没有删除成为Email email = session.get(emailId); email.setSubject(subject); 的领域的二传手。当在java空间中调用setter时,该字段的值不再与DB生成的值相匹配。所以基本上版本字段不再匹配了。此时,对实体的任何修改都会导致:

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

我在内存DB和Hibernate中使用H2。


2
投票

检查数据库中是否存在该对象,如果存在则获取该对象并刷新它:

@Version

如果它失败了上述if条件...在DB中找到带有Id的对象,执行您需要的操作,在这种情况下,确切的更改将反映出来。

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

1
投票

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


1
投票

我在项目的不同环境中遇到了相同的问题,并且有不同的场景,如

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

在第一种情况下

当我发出服务器cal时,在保存那个对象之前他们从js调用并尝试保存和另一个地方之前,我得到了,js调用是两次,三次(我调用绑定的东西导致问题)

我解决了

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

第二种情况,

e.preventDefault()

1
投票

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

实体肮脏是通过比较给定对象的每个属性(与其等于方法)或object.lock() (如果属性具有关联的UserType.equals)来完成的。

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

希望有一天能帮助某人。

谢谢Rags

© www.soinside.com 2019 - 2024. All rights reserved.