82

我有一个在网络服务器上运行的 java 项目。我总是遇到这个异常。

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

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

我的方法是这样的:

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

尽管:

  • Email是一个 Hibernate 类(它将是数据库中的一个表)
  • getEmailById(String id)是一个返回 an 的函数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 
4

21 回答 21

66

悲观锁一般不被推荐,并且在数据库端的性能方面非常昂贵。您提到的问题(代码部分)有几件事不清楚,例如:

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

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

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

编辑

有一种方法可以在 Hibernate 中使用悲观锁定。看看这个链接。但是这种机制似乎存在一些问题。但是,我发现在休眠(HHH-5275)中发布了一个错误。bug中提到的场景如下:

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

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

于 2011-12-27T14:58:53.503 回答
20

我们有一个队列管理器,它轮询数据并将其提供给处理程序进行处理。为了避免再次拾取相同的事件,队列管理器使用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()方法事务提交时,处理程序(不同的线程)在下面更改了对象。

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

于 2013-09-27T01:41:22.800 回答
16

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

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

    @Transactional
    public void test(String id, String subject) {
       Email email = getEmailById(id);
       email.setSubject(subject);
       updateEmail(email);
    }
于 2011-12-27T14:29:17.367 回答
10

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

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

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

我在内存数据库和休眠中使用 H2。

于 2015-02-09T13:56:40.797 回答
6

此异常可能是由乐观锁定(或代码中的错误)引起的。你可能在不知不觉中使用它。而且您的伪代码(应该用真实代码替换以能够诊断问题)是错误的。Hibernate 会自动保存对附加实体所做的所有修改。您永远不应该在附加实体上调用更新、合并或保存或更新。做就是了

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

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

于 2011-12-27T14:34:54.707 回答
3

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

通过比较给定对象的每个属性(使用它们的 equals 方法)或UserType.equals属性是否具有关联的org.Hibernate.UserType.

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

希望有一天这对某人有所帮助。

谢谢 破布

于 2015-11-27T00:40:47.163 回答
2

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

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

如果它不符合上述如果条件...在数据库中找到具有 Id 的对象,执行您需要的操作,在这种情况下,确切的更改将反映。

if (....) {
    } else if (null != identity) {
        E dbInstance = (E) getEntityManager().find(instance.getClass(), identity);
        return dbInstance;
    }
于 2012-10-23T11:27:30.570 回答
2

我在项目的不同背景下遇到了同样的问题,并且有不同的场景,比如

 - 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()
于 2014-05-23T06:16:18.190 回答
2

以防万一有人检查了这个线程并遇到了和我一样的问题......

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

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

我手动传递了密钥,并在映射中指定了一个 GUID 生成器......

并且休眠为我生成了完全相同的错误,所以一旦我删除了 GUID,并将该字段留空,一切都很好。

这个答案可能对你没有帮助,但会帮助像我这样因为同样的错误而刚刚查看你的帖子的人

于 2015-01-25T10:52:38.647 回答
2

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

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

在这里您可以找到更多信息。

于 2019-01-10T15:46:33.327 回答
2

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

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

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

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

于 2021-02-08T07:50:28.187 回答
1

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

于 2014-11-05T17:18:03.533 回答
1

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

LocalDate在应该使用的时候使用了LocalDateTime——我认为这导致 hibernate 无法区分实体,从而导致此错误。

将密钥更改为 aLocalDateTime后,错误消失了。此外,更新各个行也开始起作用——以前它会找不到要更新的行,而测试这个单独的问题实际上是我得出关于主键映射的结论的原因。

于 2019-04-01T23:15:01.957 回答
1

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

于 2019-06-14T12:06:08.970 回答
0

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

class Entity {
    List collection

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

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

class Entity {
    List collection

    List getUniqueCollection() {
        return collection.unique()
    }
}
于 2017-05-04T08:11:34.740 回答
0

如果您将 Hibernate 与 Dropwizard 一起使用,则如果您使用 id 作为自动生成的,则可能会发生这种情况。删除@GeneratedValue

在此处输入图像描述

于 2021-08-06T11:45:53.683 回答
0

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

因此,当您更新实体时,如果不需要,请不要在 json 正文中包含版本。只需在版本列中使用@Version 进行注释。

于 2021-10-07T12:36:02.963 回答
0

一、错误原因

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

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

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

{
    "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.求解

删除错误数据。

于 2022-01-14T03:44:10.313 回答
-1

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

<timestamp name="lstUpdTstamp" column="LST_UPD_TSTAMP" source="db"/>
于 2015-10-30T11:27:41.587 回答
-1

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

于 2018-10-24T14:34:45.563 回答
-3

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

于 2015-04-22T20:23:29.753 回答