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


69

我有一个在网络服务器上运行的Java项目。我总是碰到这个例外。

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

但是我找不到任何清晰的示例来说明如何使用它。

我的方法是这样的:

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

而:

  • Email 是一个休眠类(它将是数据库中的一个表)
  • 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 

1
您确定数据库没有从多个位置更新,并且唯一的代码就是这样做的吗?还test(Email email, String Subject)可以同时访问您的方法吗?
桑托什

我使用postgre数据库。没有我的项目仅在我的PC上仍在测试中,因此没有人可以访问它,我只是谁更新数据库上的数据,我创建了每30秒自动调用此方法的服务,此服务是调用此方法的唯一服务
格鲁吉亚公民

Answers:


61

通常不建议使用悲观锁定,因为从数据库方面考虑,这种锁定的成本很高。您所提到的问题(代码部分)尚不清楚,例如:

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

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

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

编辑

在Hibernate中有一种使用悲观锁定的方法。查看此链接。但是这种机制似乎存在一些问题。但是,我偶然发现了一个休眠状态的错误(HHH-5275)。该错误中提到的情况如下:

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

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


我知道,这就是为什么我需要锁定数据库,直到当前会话完成其更新。如何锁定数据库(乐观/悲观)?如果可以的话,请通过代码通知我。我使用spring,但这是我项目中的一个小例子。谢谢
格鲁吉亚公民

由于您的解决方案在搜索列表结果中排名很高,因此我想问您您的意思是:一个线程成功。该线程是否提交实体?
Stephane 2015年

是。成功表示线程能够完成事务(或提交实体)。
桑托什2015年

“实体”是指数据库而不是表中的同一行。对?
Aseem Goyal's

是。我的意思是一样的。
桑托什

17

我知道这是一个古老的问题,但我们中的一些人仍在解决这个问题,并看着天空如何飘荡。这是我面临的一种问题,

我们有一个队列管理器,它轮询数据并交给处理程序进行处理。为了避免再次发生相同的事件,队列管理器将记录锁定为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


“ @Transactional”旁边的“必填”是什么意思?
tk_

2
这是传播,我只是没有在那输入所有信息,实际上是@Transactional(propagation = Propagation.REQUIRED)请检查docs.spring.io/spring/docs/4.2.x/spring-framework-reference/…
Lyju I Edwinson

4
您如何解决此问题?是通过添加@Transactional(propagation = Propagation.REQUIRED,readOnly = false)还是将其删除?抱歉,我对此不太理解。

通过删除poll方法上的@Transactional,这将使dao.getLockedEntity()上的Transaction在调用队列方法之前完成。因此,当事务使用队列方法排队时,任务处理程序现在可以修改对象,因为事务已经完成,并且这些对象已从dao.getLockedEntity()创建的休眠会话中分离出来。
Lyju I Edwinson

14

看起来您实际上并没有使用从数据库中检索到的电子邮件,而是使用了作为参数获取的较旧的副本。在检索以前的版本与进行更新之间,该行上用于版本控制的任何内容都已更改。

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

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

1
确保版本是问题所在。但是这种方法上的更改并没有影响我多次尝试此解决方案。如果您有其他解决方案,请在此处发布感谢
Georgian Citizen

9

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

实施乐观锁定后,我遇到了同样的异常。我的错误是我没有删除成为比赛场地的传教士@Version。由于在Java空间中调用了setter,因此该字段的值不再与DB生成的值匹配。因此,基本上版本字段不再匹配。那时,对该实体的任何修改都会导致:

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

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


6

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

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

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


这不是主要问题。更新功能不会导致此错误。我想在项目中使用乐观或悲观锁定,如何将其用作方案?谢谢
格鲁吉亚公民

1
乐观锁定包括检测到另一个事务已更新/删除同一行,并抛出在这种情况下要获取的异常。如果您有版本字段,那么您已经在使用乐观锁定,这就是引发异常的原因。悲观锁定将使您的应用程序变慢并遭受死锁。处理:如果多个用户正在更新同一行,则其中之一将获得异常。这是正常的和预期的。
JB Nizet

如果多个用户更新同一行而一个用户获取异常,这是不正常的,因为其他功能中的用户正在更新平衡。如果一个用户遇到异常,则余额不会更新。现在我只是访问该程序的人,如果没有几个用户访问它,我将得到此异常,如果许多用户访问它,将会发生什么情况。您知道我如何使用悲观锁定吗?
Georgian Citizen

2
如果您是唯一访问数据库的人,并且遇到此异常,则您的代码有问题。向我们显示您的代码。如果您在同一行上有多个并发用户,则有例外是正常现象。捕获异常,并通知用户其操作未成功,应刷新并重试。
JB Nizet

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

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

2

我遇到了同样的问题,在我的情况下,该问题丢失和/或在实体对象中某些类型的字段上不正确的equals实现。在提交时,Hibernate会检查会话中加载的所有实体,以检查它们是否脏。如果任何实体是脏的,休眠将尝试保留它们-无论请求保存操作的实际对象与其他实体无关。

实体污损是通过比较给定对象的每个属性(及其equals方法)或UserType.equals属性是否具有关联来完成的org.Hibernate.UserType

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

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

谢谢破布


1

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


1

万一有人检查此线程并遇到与我相同的问题...

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

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

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

这个答案可能不会帮助您,但会帮助像我这样的人,因为您的线程由于相同的错误


1

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

LocalDate应该在应该用的时候用过LocalDateTime–我认为这导致休眠状态无法区分实体,从而导致此错误。

将键更改为a后LocalDateTime,错误消失了。另外,更新单个行也开始起作用-以前它找不到要更新的行,而测试此单独的问题实际上是使我得出有关主键映射的结论的原因。


1

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


1

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

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

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


感谢您提供这个非常有用的提示:正是这个问题的解决方案使我疯狂了几个小时:)
Francesco Galgani

0

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

class Entity {
    List collection

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

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

class Entity {
    List collection

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

-1

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

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

@Yusuf-提示:为确保XML代码可见,请使用{}工具栏中的代码按钮或缩进四(4)行空格。
利于2015年

1
您应该解释您的代码在做什么以及如何解决该问题。我认为这不是解决方案。
Stealth Rabbi

-1

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


-3

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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.