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

Geo*_*zen 57 java spring hibernate amazon-web-services pessimistic-locking

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

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

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

我的方法是这样的:

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

而:

  • 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 
Run Code Online (Sandbox Code Playgroud)

San*_*osh 57

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

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

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

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

编辑:

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

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

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


tva*_*son 13

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

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

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


Lyj*_*son 9

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

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

   void poll() {
        record = dao.getLockedEntity();
        queue(record);
     }
Run Code Online (Sandbox Code Playgroud)

这种方法不是事务性的,而是dao.getLockedEntity()"必需的"事务性的.

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

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

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

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

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

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

关心Lyju

  • 你是如何解决这个问题的?是通过添加 @Transactional(propagation=Propagation.REQUIRED, readOnly=false) 还是删除它?我不是很明白,抱歉。 (6认同)
  • 这是传播,我只是没有在那里输入所有内容,实际上是@Transactional(propagation = Propagation.REQUIRED) 请检查 https://docs.spring.io/spring/docs/4.2.x/spring-framework -reference/html/transaction.html (3认同)

JB *_*zet 6

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

Email email = session.get(emailId);
email.setSubject(subject);
Run Code Online (Sandbox Code Playgroud)

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

  • 如果您是唯一访问数据库的人,并且遇到此异常,那么您的代码就有问题。向我们展示您的代码。如果您在同一行上有多个并发用户,则出现异常是正常且正常的。捕获异常,并通知用户他的操作没有成功,他应该刷新并重试。 (2认同)

小智 5

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

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

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

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