web-dev-qa-db-fra.com

La ligne a été mise à jour ou supprimée par une autre transaction (ou le mappage de valeur non enregistrée était incorrect)

J'ai un projet Java qui s'exécute sur un serveur Web. Je frappe toujours cette exception.

J'ai lu des documents et trouvé que le verrouillage pessimiste (ou optimiste, mais j'ai lu que le pessimiste est préférable) est le meilleur moyen de prévenir cette exception.

Mais je n’ai trouvé aucun exemple clair qui explique comment l’utiliser.

Ma méthode est comme:

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

tandis que:

  • Email est une classe hibernate (ce sera une table dans la base de données)
  • getEmailById(String id) est une fonction qui retourne une email (cette méthode n'est pas annotée avec @Transctional)
  • updateEmail(email): est une méthode qui met à jour le courrier électronique.

Remarque: J'utilise hibernate pour enregistrer, mettre à jour, etc. (exemple: session.getcurrentSession.save(email)).

L'éxéption:

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 
49
Georgian Citizen

Le verrouillage pessimiste n'est généralement pas recommandé et il est très coûteux en termes de performances du côté de la base de données. Le problème que vous avez mentionné (la partie code), certaines choses ne sont pas claires, telles que:

  • Si votre code est utilisé par plusieurs threads en même temps.
  • Comment créez-vous un objet session (vous ne savez pas si vous utilisez Spring)?

Hibernate Les objets de session ne sont pas thread-safe . Ainsi, si plusieurs threads accèdent à la même session et tentent de mettre à jour la même entité de base de données, votre code peut potentiellement se retrouver dans une situation d'erreur comme celle-ci. 

Donc ce qui se passe ici, c'est que plus d'un thread tente de mettre à jour la même entité, un thread réussit et lorsque le prochain thread va valider les données, il voit que celles-ci ont déjà été modifiées et finissent par lancer StaleObjectStateException.

MODIFIER:

Il existe un moyen d'utiliser le verrouillage pessimiste dans Hibernate. Découvrez ce lien . Mais il semble y avoir un problème avec ce mécanisme. Je suis cependant tombé sur un bogue en veille prolongée ( HHH-5275 ). Le scénario mentionné dans le bogue est le suivant:

Deux threads lisent le même enregistrement de base de données; un de ces fils devrait utiliser un verrouillage pessimiste bloquant ainsi l'autre thread. Mais les deux threads peuvent lire l'enregistrement de base de données, ce qui provoque l'échec du test.

Ceci est très proche de ce que vous faites face. Essayez ceci si cela ne fonctionne pas, la seule façon dont je puisse penser consiste à utiliser requêtes SQL natives où vous pouvez obtenir un verrouillage pessimiste dans postgres database avec une requête SELECT FOR UPDATE.

44
Santosh

Il ne semble pas que vous utilisiez réellement le courrier que vous avez récupéré dans la base de données, mais une copie plus ancienne que vous obtenez en tant que paramètre. Tout ce qui est utilisé pour le contrôle de version sur la ligne a changé entre le moment où la version précédente a été récupérée et le moment où vous effectuez la mise à jour.

Vous voulez probablement que votre code ressemble davantage à:

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

Je sais que c’est une vieille question, mais certains d’entre nous la frappent encore et regardent le ciel errant. Voici un type de problème que j'ai rencontré,

Nous avons un gestionnaire de files d'attente qui interroge les données et les donne aux gestionnaires à des fins de traitement. Pour éviter de reprendre les mêmes événements, le gestionnaire de files d'attente verrouille l'enregistrement dans la base de données avec l'état LOCKED.

   void poll() {
        record = dao.getLockedEntity();
        queue(record);
     }

cette méthode n'était pas transactionnelle mais dao.getLockedEntity() était transactionnelle avec 'REQUIRED'.

Tout bon et sur la route, après quelques mois de production, il a échoué avec une exception de verrouillage optimiste 

Après beaucoup de débogage et de vérification des détails, nous pourrions découvrir que quelqu'un a changé le code de cette manière,

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

Ainsi, l'enregistrement a été mis en file d'attente avant même que la transaction dans dao.getLockedEntity () soit validée (il utilise la même transaction de la méthode poll) et l'objet a été modifié par les gestionnaires (threads différents) au moment où la transaction poll () est engagé.

Nous avons corrigé le problème et tout semble aller bien maintenant.

J'ai pensé le partager, car une exception de verrouillage optimiste peut être déroutante et difficile à déboguer. Quelqu'un pourrait tirer profit de mon expérience.

Cordialement Lyju

7
Lyju I Edwinson

Cette exception est probablement due à un verrouillage optimiste (ou à un bogue dans votre code). Vous l'utilisez probablement sans le savoir. Et votre pseudo-code (qui devrait être remplacé par un code réel pour pouvoir diagnostiquer le problème) est erroné. Hibernate enregistre automatiquement toutes les modifications apportées aux entités attachées. Vous ne devez jamais appeler update, fusionner ou saveOrUpdate sur une entité attachée. Il suffit de faire

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

Pas besoin d'appeler la mise à jour. Hibernate effacera automatiquement les modifications avant de valider la transaction.

4
JB Nizet

J'ai eu ce problème sur mon projet. 

Après avoir implémenté le verrouillage optimiste, j'ai eu la même exception. Mon erreur est que je n’ai pas supprimé le sélecteur du champ qui est devenu le @Version. Comme le setter était appelé dans l'espace Java, la valeur du champ ne correspond plus à celle générée par la base de données. Donc, fondamentalement, les champs de version ne correspondaient plus. À ce stade, toute modification sur l'entité a eu pour résultat:

org.hibernate.StaleObjectStateException: La ligne a été mise à jour ou supprimée par une autre transaction (ou le mappage de valeur non sauvegardée était incorrect)

J'utilise H2 en mémoire DB et Hibernate.

3
MF Wolf

vérifie si l'objet existe ou non dans la base de données, s'il existe, récupérez-le et actualisez-le:

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

si la condition ci-dessus échoue si ... recherchez l'Objet avec ID dans la base de données, effectuez l'opération dont vous avez besoin. Dans ce cas, les modifications seront reflétées.

if (....) {
    } else if (null != identity) {
        E dbInstance = (E) getEntityManager().find(instance.getClass(), identity);
        return dbInstance;
    }
2
pavan

J'ai eu l'expérience du même problème dans différents contextes de mon projet et il y a différents scénarios comme

 - object is accessed from various source like (server side and client)
 - without any interval accessing the same object from a different place

Dans le premier cas

Lorsque je lance un appel serveur, avant de sauvegarder cet objet, j’ai appelé, js appelle deux ou trois fois (j’appelle cet appelant et j’essaie de sauver un autre endroit.

J'ai résolu par 

e.preventDefault()

Le deuxième cas, 

object.lock()

J'ai également rencontré cette erreur lors de la tentative de mise à jour d'une ligne existante après en avoir créé une nouvelle, et j'ai passé des années à me gratter la tête, à fouiller dans la logique de transaction et de version, jusqu'à ce que je réalise que J'avais utilisé le mauvais type pour l'un de mes colonnes de clé primaire.

J'ai utilisé LocalDate alors que j'aurais dû utiliser LocalDateTime - je pense que cela empêchait l'hibernation de distinguer les entités, ce qui entraînait cette erreur.

Après avoir modifié la clé pour qu'elle soit une LocalDateTime, l'erreur a disparu. En outre, la mise à jour de lignes individuelles a également commencé à fonctionner. Auparavant, il était impossible de trouver une ligne à mettre à jour et le test de cette question distincte m'a conduit à tirer mes conclusions concernant le mappage de clé primaire.

1
Josh Manderson

Juste au cas où quelqu'un vérifierait ce fil et aurait le même problème que le mien ...

La ligne a été mise à jour ou supprimée par une autre transaction (ou le mappage de valeur non enregistrée était incorrect)

J'utilise NHibernate, je reçois la même erreur lors de la création d'un objet ...

Je passais la clé manuellement, et j'ai également spécifié un générateur GUID dans le mappage, Donc hibernate génère la même erreur exacte pour moi, Donc, une fois que j'ai supprimé le GUID et laissé le champ vide, tout est allé ça va.

cette réponse ne vous aidera peut-être pas, mais aidera quelqu'un comme moi qui n'a que votre fil de discussion à cause de la même erreur

1
deadManN

J'ai eu le même problème et dans mon cas, le problème était manquant et/ou l'implémentation incorrecte correspond à certains types de champs dans l'objet entité. Au moment de la validation, Hibernate vérifie TOUTES les entités chargées dans la session pour vérifier si elles sont sales. Si l'une des entités est sale, hibernate essaie de la persister, même si l'objet demandé à une opération de sauvegarde n'est pas lié aux autres entités. 

La saleté de l'entité est obtenue en comparant chaque propriété d'un objet donné (avec ses méthodes égales) ou UserType.equals si la propriété a un org.Hibernate.UserType associé.

Une autre chose qui m'a surpris est que dans ma transaction (en utilisant l'annotation Spring @Transactional), je traitais avec une seule entité. Hibernate se plaignait d'une entité aléatoire qui n'était pas liée à son enregistrement. J'ai réalisé qu'il existait une transaction la plus externe que nous créions au niveau du contrôleur REST. La portée de la session est donc trop grande. Par conséquent, tous les objets déjà chargés dans le cadre du traitement des demandes sont vérifiés.

J'espère que cela aidera quelqu'un, un jour.

Merci chiffons

1
user2639828

Cette erreur s'est produite lorsque j'ai essayé de mettre à jour la même ligne à partir de 2 sessions différentes. J'ai mis à jour un champ dans un navigateur alors qu'un deuxième était ouvert et avait déjà stocké l'objet d'origine dans sa session. Lorsque j'ai tenté de mettre à jour cette deuxième session "obsolète", l'erreur d'objet obsolète s'est produite. Afin de corriger cela, je récupère mon objet pour qu'il soit mis à jour à partir de la base de données avant de définir la valeur à mettre à jour, puis enregistrez-le comme d'habitude.

1
Roger

J'ai eu le même problème dans mon projet de Grails. Le bogue était, que j'écrase la méthode getter d'un champ de collection. Cela renvoyait toujours une nouvelle version de la collection dans un autre thread.

class Entity {
    List collection

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

La solution consistait à renommer la méthode getter:

class Entity {
    List collection

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

Ne définissez pas un Id sur l'objet que vous enregistrez car le Id sera généré automatiquement.

0
w0ns88