{"record":{"id":"4fa1ed4237e157ed","repo":"hibernate/hibernate-orm","slug":"cannot-update-previous-revision-for-entity-s-and","errorCode":null,"errorMessage":"Cannot update previous revision for entity %s and id %s (%s rows modified).","messagePattern":"Cannot update previous revision for entity (.+?) and id (.+?) \\((.+?) rows modified\\)\\.","errorType":"exception","errorClass":"AuditException","httpStatus":null,"severity":"error","filePath":"hibernate-core/src/main/java/org/hibernate/persister/entity/mutation/EntityAuditSupport.java","lineNumber":437,"sourceCode":"\t\t}\n\n\t\tsourceMappings[tableIndex].getKeyMapping().breakDownKeyJdbcValues(\n\t\t\t\tid,\n\t\t\t\t(jdbcValue, columnMapping) -> jdbcValueBindings.bindValue(\n\t\t\t\t\t\tjdbcValue, columnMapping.getColumnName(), ParameterUsage.RESTRICT\n\t\t\t\t),\n\t\t\t\tsession\n\t\t);\n\t}\n\n\tpublic static boolean verifyTransactionEndOutcome(\n\t\t\tint affectedRowCount,\n\t\t\tModificationType modificationType,\n\t\t\tString entityName,\n\t\t\tObject id) {\n\t\tif ( affectedRowCount > 1\n\t\t\t\t|| affectedRowCount == 0 && modificationType != ModificationType.ADD ) {\n\t\t\tthrow new AuditException(\n\t\t\t\t\t\"Cannot update previous revision for entity \"\n\t\t\t\t\t\t\t+ entityName + \" and id \" + id\n\t\t\t\t\t\t\t+ \" (\" + affectedRowCount + \" rows modified).\"\n\t\t\t);\n\t\t}\n\t\treturn true;\n\t}\n\n\tprivate EntityTableMapping[] buildAuditTableMappings() {\n\t\tfinal EntityTableMapping[] sourceMappings = entityPersister.getTableMappings();\n\t\tfinal EntityTableMapping[] result = new EntityTableMapping[sourceMappings.length];\n\t\tfor ( int i = 0; i < sourceMappings.length; i++ ) {\n\t\t\tfinal EntityTableMapping source = sourceMappings[i];\n\t\t\tif ( source.isInverse() ) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tfinal String auditTableName = auditMapping.resolveTableName( source.getTableName() );\n\t\t\tresult[i] = createAuxiliaryTableMapping( source, entityPersister, auditTableName );","sourceCodeStart":419,"sourceCodeEnd":455,"githubUrl":"https://github.com/hibernate/hibernate-orm/blob/fad1729dce015f908198d57a8d80274a30f905a5/hibernate-core/src/main/java/org/hibernate/persister/entity/mutation/EntityAuditSupport.java#L419-L455","documentation":"At transaction end the audit coordinator 'closes' the previous revision by updating it; verifyTransactionEndOutcome expects exactly one affected row (zero tolerated only for ModificationType.ADD). AuditException reports either more than one matched row (multiple revision rows matched the close predicate) or zero rows for a non-ADD change (the revision row that should exist is gone). Both indicate the audit rows were changed or removed outside the coordinator's assumptions.","triggerScenarios":"Concurrent transactions revising the same entity + id so a revision-close update matches several rows; audit revision rows deleted or modified by external jobs, scripts, or DBAs while the application runs; test runs against a dirty audit schema with leftover revision rows.","commonSituations":"Retention/archival jobs purging audit rows live; hot entities updated concurrently by users and batch jobs; environments where audit tables are hand-edited for corrections.","solutions":["Stop external deletion/modification of audit rows while transactions run - archive via a controlled export instead","Treat it like an optimistic-lock failure: rollback and retry the unit of work","Serialize updates to hot entities (e.g. pessimistic lock on the entity) so revision close never races","Inspect the audit table for duplicate or missing revision rows for the reported entity/id and repair the data"],"exampleFix":null,"handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"for (int attempt = 0; attempt < 3; attempt++) {\n    try {\n        tx = begin(); doWork(); tx.commit(); break;\n    } catch (AuditException e) {\n        rollback();\n        if (!e.getMessage().contains(\"rows modified\")) throw e;\n        backoff(attempt); // concurrent revision close: retry the unit of work\n    }\n}","preventionTips":["Never delete or rewrite audit revision rows while the application is running","Serialize writes to hot entities so revision-close updates cannot race","Run retention/archival jobs offline or under an exclusive maintenance window"],"tags":["hibernate","orm","auditing","concurrency","data-integrity","transaction"],"backgroundTag":"concurrent-modification-detected","analyzedSha":"fad1729dce015f908198d57a8d80274a30f905a5","analyzedAt":"2026-08-22T04:13:57.527Z","schemaVersion":2},"datasetVersion":"2026-08-22T09:17:25.309Z"}