Строка была обновлена ​​или удалена другой транзакцией (или отображение несохраненных значений было неверным) - PullRequest
55 голосов
/ 27 декабря 2011

У меня есть проект 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 

Ответы [ 17 ]

50 голосов
/ 27 декабря 2011

Пессимистическая блокировка, как правило, не рекомендуется, и это очень дорого с точки зрения производительности на стороне базы данных. Проблема, о которой вы упомянули (часть кода), несколько вещей не ясны, такие как:

  • Если к вашему коду обращаются одновременно несколько потоков.
  • Как вы создаете session объект (не уверен, используете ли вы Spring)?

Hibernate Объекты сеанса НЕ являются поточно-ориентированными . Таким образом, если несколько потоков обращаются к одному и тому же сеансу и пытаются обновить один и тот же объект базы данных, ваш код потенциально может оказаться в ситуации ошибки, подобной этой.

Итак, что здесь происходит, так это то, что более одного потока пытается обновить один и тот же объект, один поток завершается успешно, и когда следующий поток переходит к фиксации данных, он видит, что они уже были изменены, и в итоге выдает StaleObjectStateException.

EDIT

В Hibernate есть способ использовать пессимистическую блокировку. Проверьте эту ссылку . Но, похоже, есть некоторые проблемы с этим механизмом. Однако я столкнулся с сообщением об ошибке в спящем режиме ( HHH-5275 ). Сценарий, упомянутый в ошибке, выглядит следующим образом:

Два потока читают одну и ту же запись в базе данных; одна из этих тем следует использовать пессимистическую блокировку, таким образом блокируя другой поток. Но оба потока могут прочитать запись базы данных, что привело к сбою теста.

Это очень близко к тому, с чем вы сталкиваетесь. Пожалуйста, попробуйте это, если это не работает, единственный способ, который я могу придумать, - это использовать Собственные запросы SQL , где можно добиться пессимистической блокировки в базе данных postgres с запросом SELECT FOR UPDATE.

13 голосов
/ 27 декабря 2011

Не похоже, что вы на самом деле используете адрес электронной почты, который вы получаете из базы данных, но более старую копию, которую вы получаете в качестве параметра. Все, что используется для контроля версий в строке, изменилось между моментом получения предыдущей версии и обновлением.

Вы, вероятно, хотите, чтобы ваш код выглядел больше как:

@Transactional
Public void test(String id, String subject){
   Email email = getEmailById(id);
   email.setSubject(subject);
   updateEmail(email);
}
7 голосов
/ 27 сентября 2013

Я знаю, что это старый вопрос, но некоторые из нас все еще бьют по нему и смотрят на небо, блуждая как. Вот одна проблема, с которой я столкнулся,

У нас есть менеджер очередей, который опрашивает данные и передает их обработчикам для обработки. Чтобы избежать повторного получения тех же событий, менеджер очередей блокирует запись в базе данных с состоянием 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

5 голосов
/ 09 февраля 2015

У меня была эта проблема в моем проекте.

После того, как я реализовал оптимистическую блокировку, я получил то же исключение. Моя ошибка заключалась в том, что я не удалил установщик поля, которое стало @Version. Поскольку сеттер вызывался в Java-пространстве, значение поля больше не совпадало с тем, которое генерировало БД. Так что в основном поля версий больше не совпадают. В этот момент любая модификация объекта привела к:

org.hibernate.StaleObjectStateException: строка была обновлена ​​или удалена другая транзакция (или сопоставление несохраненного значения было неверным)

Я использую H2 в памяти DB и Hibernate.

4 голосов
/ 27 декабря 2011

Это исключение, вероятно, вызвано оптимистической блокировкой (или ошибкой в ​​вашем коде). Вы, вероятно, используете это, не зная. И ваш псевдокод (который должен быть заменен реальным кодом для диагностики проблемы) неверен. Hibernate автоматически сохраняет все изменения, сделанные для прикрепленных объектов. Вы никогда не должны вызывать update, merge или saveOrUpdate для присоединенного объекта. Просто сделай

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

Нет необходимости вызывать обновление. Hibernate автоматически сбросит изменения перед совершением транзакции.

2 голосов
/ 23 октября 2012

проверить, существует ли объект в БД, если он существует, получить объект и обновить его:

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

если не выполнено вышеприведенное условие, если условие ... найти объект с идентификатором в БД, выполните ту операцию, которая вам нужна, в этом случае точно изменения будут отражены.

if (....) {
    } else if (null != identity) {
        E dbInstance = (E) getEntityManager().find(instance.getClass(), identity);
        return dbInstance;
    }
1 голос
/ 14 июня 2019

Не устанавливайте Id для объекта, который вы сохраняете, так как Id будет автоматически сгенерирован

1 голос
/ 02 апреля 2019

Я также столкнулся с этой ошибкой, пытаясь обновить существующую строку после создания новой, и целую вечность чесал голову, копался в логике транзакций и версий, пока не понял, что Я использовал неправильный тип дляодин из моих столбцов первичного ключа.

Я использовал LocalDate, когда должен был использовать LocalDateTime - я думаю, что это приводило к тому, что hibernate не мог различать объекты, что приводило к этой ошибке.

После изменения ключа на LocalDateTime ошибка исчезла.Кроме того, начало работать также обновление отдельных строк - раньше не было возможности найти строку для обновления, и тестирование этой отдельной проблемы фактически привело меня к моим выводам относительно сопоставления первичного ключа.

1 голос
/ 27 ноября 2015

У меня была та же проблема, и в моем случае проблема отсутствовала и / или некорректно соответствовала реализации на некоторых типах полей в объекте сущности. Во время фиксации Hibernate проверяет ВСЕ объекты, загруженные в сеанс, чтобы проверить, не загрязнены ли они. Если какой-либо из объектов является грязным, hibernate пытается сохранить их - независимо от того, что фактический объект, который запрашивает операцию сохранения, не связан с другими объектами.

Грязность сущности делается путем сравнения каждого свойства данного объекта (с их методами equals) или UserType.equals, если у свойства есть связанный org.Hibernate.UserType.

Еще одна вещь, которая удивила меня, заключалась в том, что в моей транзакции (с использованием аннотации Spring @Transactional) я имел дело с одной сущностью. Hibernate жаловался на какую-то случайную сущность, которая не связана с сохранением этой сущности. Я понял, что существует самая внешняя транзакция, которую мы создаем на уровне контроллера REST, поэтому область действия сеанса слишком велика, и, следовательно, все объекты, когда-либо загруженные в ходе обработки запроса, проверяются на предмет «грязности».

Надеюсь, это когда-нибудь кому-нибудь поможет.

Спасибо, Тряпки

1 голос
/ 25 января 2015

На всякий случай, если кто-то проверил этот поток и имел ту же проблему, что и мой ...

Строка была обновлена ​​или удалена другой транзакцией (или отображение несохраненного значения было неверным)

Я использую NHibernate, я получаю ту же ошибку при создании объекта ...

Я передавал ключ вручную, а также указал генератор GUID в отображении, поэтому Hibernate генерирует точно так жеошибка для меня, поэтому, как только я удалил GUID и оставил поле пустым, все прошло просто отлично.

этот ответ может не помочь вам, но поможет кому-то, как я, кто только ваш поток из-за той же ошибки

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...