hibernate/hibernate-orm · error · AuditException
Cannot update previous revision for entity %s and id %s (%s
Error message
Cannot update previous revision for entity %s and id %s (%s rows modified).
What it means
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.
Source
Thrown at hibernate-core/src/main/java/org/hibernate/persister/entity/mutation/EntityAuditSupport.java:437
}
sourceMappings[tableIndex].getKeyMapping().breakDownKeyJdbcValues(
id,
(jdbcValue, columnMapping) -> jdbcValueBindings.bindValue(
jdbcValue, columnMapping.getColumnName(), ParameterUsage.RESTRICT
),
session
);
}
public static boolean verifyTransactionEndOutcome(
int affectedRowCount,
ModificationType modificationType,
String entityName,
Object id) {
if ( affectedRowCount > 1
|| affectedRowCount == 0 && modificationType != ModificationType.ADD ) {
throw new AuditException(
"Cannot update previous revision for entity "
+ entityName + " and id " + id
+ " (" + affectedRowCount + " rows modified)."
);
}
return true;
}
private EntityTableMapping[] buildAuditTableMappings() {
final EntityTableMapping[] sourceMappings = entityPersister.getTableMappings();
final EntityTableMapping[] result = new EntityTableMapping[sourceMappings.length];
for ( int i = 0; i < sourceMappings.length; i++ ) {
final EntityTableMapping source = sourceMappings[i];
if ( source.isInverse() ) {
continue;
}
final String auditTableName = auditMapping.resolveTableName( source.getTableName() );
result[i] = createAuxiliaryTableMapping( source, entityPersister, auditTableName );View on GitHub (pinned to fad1729dce)
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
Defensive patterns
Strategy: retry
Try / catch
for (int attempt = 0; attempt < 3; attempt++) {
try {
tx = begin(); doWork(); tx.commit(); break;
} catch (AuditException e) {
rollback();
if (!e.getMessage().contains("rows modified")) throw e;
backoff(attempt); // concurrent revision close: retry the unit of work
}
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- Newer version [" + latestVersion + "] of entity [" + infoStr
- No row with the given identifier exists
- No audit mapping available for %s
- Unable to resolve audit table mapping for %s
- Row was already updated or deleted by another transaction
AI-assisted analysis of hibernate/hibernate-orm@fad1729dce (2026-08-22).
Data as JSON: /api/errors/4fa1ed4237e157ed.
Report an issue: GitHub.