Строка была обновлена или удалена другой транзакцией (или отображение несохраненного значения было неверным)
У меня есть проект 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
26 ответов
Пессимистическая блокировка обычно не рекомендуется, и это очень дорого с точки зрения производительности на стороне базы данных. Проблема, о которой вы упомянули (часть кода) несколько вещей, не ясны, такие как:
- Если к вашему коду обращаются несколько потоков одновременно.
- Как вы создаете
session
объект (не уверен, что вы используете Spring)?
Hibernate Session объекты НЕ являются потокобезопасными. Таким образом, если несколько потоков обращаются к одному и тому же сеансу и пытаются обновить один и тот же объект базы данных, ваш код потенциально может оказаться в такой ситуации ошибки.
Итак, что здесь происходит, так это то, что более одного потока пытается обновить одну и ту же сущность, один поток завершается успешно, и когда следующий поток переходит к фиксации данных, он видит, что они уже были изменены, и заканчивает тем, что выбрасывает StaleObjectStateException
,
РЕДАКТИРОВАТЬ:
В Hibernate есть способ использовать пессимистическую блокировку. Проверьте эту ссылку. Но, похоже, есть некоторые проблемы с этим механизмом. Однако я наткнулся на сообщение об ошибке в спящем режиме ( HHH-5275). Сценарий, упомянутый в ошибке, выглядит следующим образом:
Два потока читают одну и ту же запись базы данных; один из этих потоков должен использовать пессимистическую блокировку, тем самым блокируя другой поток. Но оба потока могут читать записи базы данных, что приводит к сбою теста.
Это очень близко к тому, с чем вы сталкиваетесь. Пожалуйста, попробуйте это, если это не работает, единственный способ, которым я могу думать, - это использовать запросы Native SQL, где вы можете добиться пессимистической блокировки в базе данных postgres с помощью SELECT FOR UPDATE
запрос.
Я знаю, что это старый вопрос, но некоторые из нас все еще бьют по нему и смотрят на небо, блуждая как. Вот одна проблема, с которой я столкнулся,
У нас есть менеджер очередей, который опрашивает данные и передает их обработчикам для обработки. Чтобы избежать повторного получения тех же событий, менеджер очередей блокирует запись в базе данных с состоянием LOCKED.
void poll() {
record = dao.getLockedEntity();
queue(record);
}
этот метод не был транзакционным, но dao.getLockedEntity()
был транзакцией с 'ТРЕБУЕТСЯ'.
Все хорошо и в дороге, после нескольких месяцев в производстве, он потерпел неудачу с оптимистичным исключением блокировки,
После многих отладок и проверки деталей мы могли бы узнать, что кто-то изменил код следующим образом:
@Transactional(propagation=Propagation.REQUIRED, readOnly=false)
void poll() {
record = dao.getLockedEntity();
queue(record);
}
Таким образом, запись была поставлена в очередь еще до того, как транзакция в dao.getLockedEntity() была зафиксирована (она использует ту же транзакцию метода poll), и объект был изменен обработчиками (различными потоками) к тому времени, когда транзакция метода poll() получила comitted.
Мы исправили проблему, и теперь она выглядит хорошо.
Я подумал о том, чтобы поделиться им, потому что исключение из-за оптимистической блокировки может сбить с толку и его трудно отладить. Кто-то может получить пользу от моего опыта.
С уважением Лю
Не похоже, что вы на самом деле используете электронную почту, полученную из базы данных, но более старую копию, которую вы получаете в качестве параметра. Все, что используется для контроля версий в строке, изменилось между моментом получения предыдущей версии и обновлением.
Вы, вероятно, хотите, чтобы ваш код выглядел больше так:
@Transactional
Public void test(String id, String subject){
Email email = getEmailById(id);
email.setSubject(subject);
updateEmail(email);
}
У меня была эта проблема на моем проекте.
После того, как я реализовал оптимистическую блокировку, я получил то же исключение. Моя ошибка состояла в том, что я не удалил сеттер поля, который стал @Version
, Поскольку сеттер вызывался в Java-пространстве, значение поля больше не совпадало с тем, которое генерировало БД. Так что в основном поля версий больше не совпадают. В этот момент любая модификация объекта привела к:
org.hibernate.StaleObjectStateException: строка была обновлена или удалена другой транзакцией (или сопоставление несохраненного значения было неправильным)
Я использую H2 в памяти DB и Hibernate.
Это исключение, вероятно, вызвано оптимистической блокировкой (или ошибкой в вашем коде). Вы, вероятно, используете это, не зная. И ваш псевдокод (который должен быть заменен реальным кодом для диагностики проблемы) неверен. Hibernate автоматически сохраняет все изменения, сделанные для прикрепленных объектов. Вы никогда не должны вызывать update, merge или saveOrUpdate для присоединенного объекта. Просто делать
Email email = session.get(emailId);
email.setSubject(subject);
Не нужно называть обновление. Hibernate автоматически сбросит изменения перед совершением транзакции.
У меня были проблемы с одной и той же ошибкой более чем в одном проекте Spring. Для меня общим решением было разделить мой метод службы , чтобы каждое действие INSERT, UPDATE и DELETE имело собственный метод с @Transactional. Я думаю, что эта проблема связана с внутренним управлением Spring, где взаимодействия с базой данных выполняются в конце метода, и, на мой взгляд, это точка, в которой запускается исключение.
Обновление и дальнейшие решения.
Моя проблема заключалась в том, что я запросил объект @Entity Class и изменил значение, не сохраняя его, потому что, строго говоря, он был обновлен другим запросом (вне области видимости), но поскольку этот объект был внутренним по отношению к сеансам на карте, теперь он имел другое значение, следующий запрос был заблокирован этим сообщением.
Поэтому я создал переменную и сохранил там новые значения, а затем отправил их в UpdateQuery, поэтому Hibernate не зарегистрировал никаких несохраненных изменений, и строку можно было обновить. Кажется, что Hibernate отправляет инструкцию блокировки в базу данных каждый раз, когда объект класса @Entity изменяется или, по крайней мере, локально захватывает строку с помощью первичного ключа.
На всякий случай кто-то проверил эту ветку и столкнулся с той же проблемой, что и моя...
Строка была обновлена или удалена другой транзакцией (или отображение несохраненного значения было неверным)
Я использую NHibernate, я получаю ту же ошибку, при создании объекта...
Я передавал ключ вручную, а также указал генератор GUID в отображении, поэтому hibernate выдал ту же самую ошибку для меня, поэтому, как только я удалил GUID и оставил поле пустым, все прошло нормально.
этот ответ, возможно, не поможет вам, но поможет кому-то, как я, кто только ваш поток из-за той же ошибки
У меня была та же проблема, и в моем случае проблема отсутствовала и / или некорректная реализация равно для некоторых типов полей в объекте сущности. Во время фиксации Hibernate проверяет ВСЕ объекты, загруженные в сеанс, чтобы проверить, не загрязнены ли они. Если какой-либо из объектов является грязным, hibernate пытается сохранить их - независимо от того, что фактический объект, который запрашивает операцию сохранения, не связан с другими объектами.
Грязность сущности делается путем сравнения каждого свойства данного объекта (с их методами equals) или UserType.equals
если свойство имеет связанный org.Hibernate.UserType
,
Еще одна вещь, которая удивила меня, была в моей транзакции (с использованием аннотации Spring @Transactional
), Я имел дело с одной сущностью. Hibernate жаловался на какую-то случайную сущность, которая не связана с сохранением этой сущности. Я понял, что существует внешняя транзакция, которую мы создаем на уровне контроллера REST, поэтому область действия сеанса слишком велика, и, следовательно, все объекты, когда-либо загруженные в ходе обработки запроса, проверяются на наличие ошибок.
Надеюсь, это поможет кому-нибудь когда-нибудь.
Спасибо Тряпки
Проверьте, существует ли объект в БД, если он существует, получите объект и обновите его:
if (getEntityManager().contains(instance)) {
getEntityManager().refresh(instance);
return instance;
}
если не выполнено указанное выше условие if... найдите объект с идентификатором в БД, выполните ту операцию, которая вам нужна, в этом случае изменения будут отражены.
if (....) {
} else if (null != identity) {
E dbInstance = (E) getEntityManager().find(instance.getClass(), identity);
return dbInstance;
}
Я также получил такое исключение, но проблема была в моем идентификаторе сущности. я использую UUID
и есть некоторые проблемы в том, как Spring работает с ними. Поэтому я просто добавил эту строку в свой идентификатор объекта, и он начал работать:
@Column(columnDefinition = "BINARY(16)")
Здесь вы можете найти немного больше информации.
У меня была одна и та же проблема в другом контексте моего проекта, и есть разные сценарии, такие как
- 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()
У меня была та же проблема, и для меня случай был немного другим, я использовал Spring Data JPA, и класс сущности был аннотирован аннотациями @Entity и @Table, а в поле ID у меня была аннотация @Id, но я пропустил добавление @GeneratedValue, поскольку в таблице БД было поле идентификации с автоинкрементом.
Но проблема возникла, когда мы выполняли массовую вставку для этих сущностей, и, поскольку в поле идентификатора не был указан Генератор, все сущности имели значение по умолчанию (0) в качестве поля идентификатора. и начал давать это исключение:
javax.persistence.OptimisticLockException: строка была обновлена или удалена другой транзакцией (или сопоставление несохраненных значений было неправильным):[dao.entity.OrderAssortmentReportEntity#0]
Мы добавили @GeneratedValue(strategy = GenerationType.IDENTITY) вместе с @Id, и это сработало.
Эта ошибка произошла для меня, когда я пытался обновить одну и ту же строку из двух разных сеансов. Я обновил поле в одном браузере, пока открыт второй, и уже сохранил исходный объект в своем сеансе. Когда я пытался выполнить обновление из этого второго "устаревшего" сеанса, я получаю ошибку устаревшего объекта. Чтобы исправить это, я перезагружаю свой объект для обновления из базы данных перед тем, как установить значение для обновления, затем сохраняю его как обычно.
Я также столкнулся с этой ошибкой, пытаясь обновить существующую строку после создания новой, и целую вечность чесал голову, копался в логике транзакций и версий, пока не понял, что использовал неправильный тип для одного из моих столбцов первичного ключа.,
я использовал LocalDate
когда я должен был использовать LocalDateTime
- Я думаю, что это привело к тому, что hibernate не смог различить объекты, что привело к этой ошибке.
После изменения ключа быть LocalDateTime
ошибка ушла. Кроме того, начало работать также обновление отдельных строк - раньше не было возможности найти строку для обновления, и тестирование этой отдельной проблемы фактически привело меня к моим выводам относительно сопоставления первичного ключа.
Не устанавливайте Id
к объекту, который вы сохраняете как Id
будет автоматически сгенерирован
если вы используете идентификатор как автоматически созданный, удалите идентификатор поля из конструктора
На случай, если это поможет кому-то еще в такой же ситуации: я получил это исключение при попытке удалить объект (через session.delete) и забыл, что объект также был удален из коллекции с помощью delete-orphan. Поэтому, когда появился мой вызов delete(), объект уже был каскадно удален из коллекции.
1. Причина ошибки
Есть другая ситуация: Ошибка данных.
@Column(name = "ID", unique = true, nullable = false, length = 32)
private String id;
Один из данных является пустым или нулевым. Когда внешнее значение сохраняется,
2 .решение
Удалить данные об ошибке.
Ни один из постов выше мне не помог. Оказалось, что я пытался обновить объект, который изначально не был создан.
У меня была такая же проблема в моем проекте Grails. Ошибка была в том, что я переписал метод получения поля коллекции. При этом всегда возвращалась новая версия коллекции в другом потоке.
class Entity {
List collection
List getCollection() {
return collection.unique()
}
}
Решением было переименовать метод получения:
class Entity {
List collection
List getUniqueCollection() {
return collection.unique()
}
}
В моем случае я сначала удалял объект в основном потоке, а затем создавал новый объект, заполнял его, вызывая внешние службы, и сохранял в отдельном потоке (с помощью CompletableFuture). Мне пришлось перенести удаление в часть логики CompletableFuture.
Эта проблема возникает, если вы пытаетесь обновить объект, который является одним и тем же экземпляром, но получен из другого списка/хэша/и т. д. прямо из другого подпотока.
Чтобы предотвратить StaleObjectStateException
, в вашем hbm
файл напишите ниже код:
<timestamp name="lstUpdTstamp" column="LST_UPD_TSTAMP" source="db"/>
Я тоже сталкивался с этой проблемой. Сначала проверьте импорт, когда вы используете сеанс, транзакция должна быть org.hibernate и удалите аннотацию @Transactinal. и что наиболее важно в классе Entity, если вы использовали @GeneratedValue(стратегии =GenerationType.AUTO) или что-либо другое, тогда во время создания объекта модели / создания объекта сущности не следует создавать идентификатор. окончательный вывод - если вы хотите передать идентификатор, то есть PK, затем удалите @GeneratedValue из класса сущностей.
Hibernate использует управление версиями, чтобы знать, что измененный объект, который у вас был, старше, чем тот, который в настоящее время сохраняется.
поэтому, когда вы обновляете объект, не включайте версию в тело json, если это нежелательно. просто отметьте @Version в столбце версии.
У меня была эта проблема в одном из моих приложений, теперь я знаю, что это старая ветка, но вот мое решение; По данным внутри отладчика я выяснил, что JVM фактически не загружает их должным образом, когда Hibernate пытается обновить базу данных (что на самом деле делается в другом потоке), поэтому я добавил ключевое слово "volatile" в каждое поле. сущностей. Для этого есть некоторые проблемы с производительностью, а не тяжелые объекты, разбросанные вокруг...