{"record":{"id":"1c6e4c2939ea14a8","repo":"hibernate/hibernate-orm","slug":"row-was-already-updated-or-deleted-by-another-tran-1c6e4c","errorCode":null,"errorMessage":"Row was already updated or deleted by another transaction","messagePattern":"Row was already updated or deleted by another transaction","errorType":"exception","errorClass":"StaleObjectStateException","httpStatus":null,"severity":"error","filePath":"hibernate-core/src/main/java/org/hibernate/event/internal/DefaultMergeEventListener.java","lineNumber":453,"sourceCode":"\t\t// We must clone embedded composite identifiers, or we will get\n\t\t// back the same instance that we pass in. Apply the special MERGE\n\t\t// fetch profile and perform the resolution (Session#get).\n\t\tfinal Object result =\n\t\t\t\tsession.getLoadQueryInfluencers()\n\t\t\t\t\t\t.fromInternalFetchProfile( CascadingFetchProfile.MERGE,\n\t\t\t\t\t\t\t\t() -> session.find( entityName, clonedIdentifier ) );\n\n\t\tif ( result == null ) {\n\t\t\tEVENT_LISTENER_LOGGER.detachedInstanceNotFoundInDatabase();\n\t\t\t// we got here because we assumed that an instance\n\t\t\t// with an assigned id and no version was detached\n\t\t\t// when it was really transient (or deleted)\n\t\t\tfinal Boolean knownTransient = persister.isTransient( entity, session );\n\t\t\tif ( knownTransient == Boolean.FALSE ) {\n\t\t\t\t// we know for sure it's detached (generated id\n\t\t\t\t// or a version property), and so the instance\n\t\t\t\t// must have been deleted by another transaction\n\t\t\t\tthrow new StaleObjectStateException( entityName, id );\n\t\t\t}\n\t\t\telse {\n\t\t\t\t// we know for sure it's transient, or we just\n\t\t\t\t// don't have information (assigned id and no\n\t\t\t\t// version property) so keep assuming transient\n\t\t\t\tentityIsTransient( event, clonedIdentifier, copyCache );\n\t\t\t}\n\t\t}\n\t\telse {\n\t\t\t// before cascade!\n\t\t\tcopyCache.put( entity, result, true );\n\t\t\tfinal Object target = targetEntity( event, entity, persister, id, result );\n\t\t\t// cascade first, so that all unsaved objects get their\n\t\t\t// copy created before we actually copy\n\t\t\tcascadeOnMerge( session, persister, entity, copyCache );\n\n\t\t\tfinal var interceptor = session.getInterceptor();\n\t\t\tfinal String[] propertyNames = persister.getPropertyNames();","sourceCodeStart":435,"sourceCodeEnd":471,"githubUrl":"https://github.com/hibernate/hibernate-orm/blob/fad1729dce015f908198d57a8d80274a30f905a5/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultMergeEventListener.java#L435-L471","documentation":"Error \"Row was already updated or deleted by another transaction\" thrown in hibernate/hibernate-orm.","triggerScenarios":"Thrown when a flush detects that the database row was concurrently modified or removed by another transaction (stale optimistic state).","commonSituations":"Typical situations: concurrent transactions updating the same row; long-running transactions with optimistic locking; reusing stale entities across transactions.","solutions":["Use optimistic locking (@Version) and catch OptimisticLockException to retry with fresh data.","Reload the entity before merging when concurrent updates are expected."],"exampleFix":null,"handlingStrategy":null,"validationCode":null,"typeGuard":null,"tryCatchPattern":null,"preventionTips":[],"tags":[],"backgroundTag":null,"analyzedSha":"fad1729dce015f908198d57a8d80274a30f905a5","analyzedAt":"2026-08-22T04:13:57.527Z","schemaVersion":2},"datasetVersion":"2026-08-22T09:17:25.309Z"}