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

时间:2011-12-27 14:25:15

标签: java hibernate spring amazon-web-services pessimistic-locking

我有一个在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进行保存,更新和放大等等(例如: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 

19 个答案:

答案 0 :(得分:46)

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

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

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

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

修改

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

  

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

这非常接近你所面对的。如果这不起作用,请尝试此操作,我能想到的唯一方法是使用Native SQL queries,您可以使用SELECT FOR UPDATE查询来实现pessimistic locking in postgres数据库。

答案 1 :(得分:13)

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

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

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

答案 2 :(得分:7)

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

我们有一个队列管理器,可以轮询数据并提供给处理程序进行处理。为避免再次拾取相同的事件,队列管理器将数据库中的记录锁定为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方法相同的事务)并且在poll()之前由处理程序(不同的线程)更改了对象方法事务得到了评估。

我们解决了这个问题,现在看起来很不错。

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

此致 Lyju

答案 3 :(得分:4)

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

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

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

答案 4 :(得分:3)

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

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

  

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

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

答案 5 :(得分:2)

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

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

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

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

答案 6 :(得分:1)

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

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

在第一种情况下

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

我解决了

e.preventDefault()

第二种情况,

object.lock()

答案 7 :(得分:1)

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

答案 8 :(得分:1)

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

  

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

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

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

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

答案 9 :(得分:1)

我在多个 Spring 项目中遇到了同样的错误。 对我来说,一个通用的解决方案是,为了拆分我的服务方法每个 INSERT、UPDATE 和 DELETE 操作都有一个自己的带有 @Transactional 的方法。 我认为这个问题与内部 Spring 管理有关,在该方法的末尾执行数据库交互,在我看来,这是触发 Exception 的点。

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

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

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

答案 10 :(得分:1)

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

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

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

希望有一天能帮到某人。

谢谢Rags

答案 11 :(得分:1)

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

答案 12 :(得分:1)

在创建新行之后尝试更新现有行时,我也遇到了此错误,并花了很长时间才摸索着头,仔细研究了事务和版本逻辑,直到我意识到我将错误的类型用于我的主键列之一。

本应使用LocalDateTime时使用了LocalDateTime –我认为这导致休眠状态无法区分实体,从而导致此错误。

将密钥更改为/html/body/table[3]/tbody/tr/td[1]/table/tbody/tr[4]/td[2]/font后,错误消失了。另外,更新单个行也开始起作用-以前它找不到要更新的行,而测试这个单独的问题实际上是使我得出有关主键映射的结论的原因。

答案 13 :(得分:0)

如果您使用 id 作为自动生成,则从构造函数中删除字段 id

答案 14 :(得分:0)

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

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

Here,您可以找到更多信息。

答案 15 :(得分:0)

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

define.py

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

Traceback (most recent call last):
  File "define.py", line 5, in <module>
    import main
  File "/path/to/main.py", line 9, in <module>
    import define
  File "/path/to/define.py", line 10, in <module>
    main.Things.hello = hello
AttributeError: module 'main' has no attribute 'Things'

答案 16 :(得分:-1)

我也遇到了这个问题。首先检查您的导入,当您使用会话时,事务应该是org.hibernate 并删除@Transactinal批注。在实体类中最重要,如果您使用@GeneratedValue(strategy = GenerationType.AUTO)或其他方法,那么在创建模型对象/实体对象时不应创建id。 最后的结论是,如果您希望将通行证ID归档,即PK,然后从实体类中删除@GeneratedValue。

答案 17 :(得分:-1)

为防止StaleObjectStateException,请在hbm文件中写下以下代码:

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

答案 18 :(得分:-3)

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