Hibernate Envers ValidityAuditStrategy throws a "Cannot update previous revision for entity" exception


#1

I created a sample project & done CRUD operations. Now I added & enabled envers, set ValidityAuditStrategy. Now on trying to update existing table rows, envers throwing exception as below. Is this an Envers bug? How to go ahead for this case?

"ERROR: HHH000099: an assertion failure occured (this may indicate a bug in Hibernate, but is more likely due to unsafe use of the session): java.lang.RuntimeException: Cannot update previous revision for entity OfficeAddress_AUD and id 1
org.hibernate.AssertionFailure: Unable to perform beforeTransactionCompletion callback
	at org.hibernate.engine.spi.ActionQueue$BeforeTransactionCompletionProcessQueue.beforeTransactionCompletion(ActionQueue.java:905)
	at org.hibernate.engine.spi.ActionQueue.beforeTransactionCompletion(ActionQueue.java:481)
	at org.hibernate.internal.SessionImpl.beforeTransactionCompletion(SessionImpl.java:2353)
	at org.hibernate.engine.jdbc.internal.JdbcCoordinatorImpl.beforeTransactionCompletion(JdbcCoordinatorImpl.java:485)
	at org.hibernate.resource.transaction.backend.jdbc.internal.JdbcResourceLocalTransactionCoordinatorImpl.beforeCompletionCallback(JdbcResourceLocalTransactionCoordinatorImpl.java:147)
	at org.hibernate.resource.transaction.backend.jdbc.internal.JdbcResourceLocalTransactionCoordinatorImpl.access$100(JdbcResourceLocalTransactionCoordinatorImpl.java:38)
	at org.hibernate.resource.transaction.backend.jdbc.internal.JdbcResourceLocalTransactionCoordinatorImpl$TransactionDriverControlImpl.commit(JdbcResourceLocalTransactionCoordinatorImpl.java:231)
	at org.hibernate.engine.transaction.internal.TransactionImpl.commit(TransactionImpl.java:65)"

#2

Try to replicate it with this test case template.


#3

The ValidityAuditStrategy expects that a prior change data capture (CDC) row exists with the same primary key where the REV_END column is NULL. If no row exists with this requirement when an update to an entity occurs, this strategy will throw this exception because the prior row state cannot be updated correctly to reflect the proper chain of REV_END values.

So why would the prior row not exist?

One common mistake is to allow insertion of audited data through means that completely bypass the event system of Hibernate. Examples of this include direct SQL manipulation, native-queries, or even the use of Hibernate’s Stateless session.

Another common reason this can happen is when Envers is enabled on an existing database with data while using the ValidityAuditStrategy implementation. In short, what happens is future changes get tracked but because no prior INSERT operation was ever audited, the strategy asserts with this failure.

Its generally accepted practice that if you intend to enable Envers on an existing legacy database, make sure that you seed the audit tables after they’ve been generated before manipulating the entities. This makes sure that the seeded data represents the current INSERT snapshot of all your audited data prior to future UPDATE changes being applied.


#4

Hi Naros,
Noticed a bug you’re working on in this respect.
https://hibernate.atlassian.net/browse/HHH-8456

If we have a big size database, can you suggest any best practice/example to seed audit tables?


#5

The basic gist to seed the audit tables is quite straight forward.

  1. Get the next REVISION_NUMBER sequence.
  2. Insert a new row into REVINFO which includes the revision number from (1) and the timestamp.
  3. Insert a new row into all the generated _AUD tables based on your current snapshot of data.

For (3) there are a couple requirements.

  1. The REVEND field (only applicable for ValidityAuditStrategy) should be left NULL.
  2. The REV field should be populated with the revision number you inserted into REVINFO.
  3. The REV_TYPE field should be set to 0 to represent an insert/add operation.
  4. If any audit tables have a _MOD field, it should be set to 1 when seeding.

The how you get data into the audit tables is entirely a personal preference. The most straight forward way is to write a database script that does it.

If you already have Debezium/Kafka installed somewhere you could also look into reading a database snapshot of the tables of interest and have a set of custom sink connectors that process those events to generate the inserts accordingly too. This is definitely far more involved, requires a substantial amount of knowledge of Debezium/Kafka Connect to work, but its another viable alternative if you’re not so familiar with writing database scripts.