Reputation: 462
I'm using Hibernate Envers to persist object history. At certain points we want to capture a snapshot of the object graph state - and we can do this by knowing the corresponding Envers revision which we then store on an audit record.
However we have a problem. The parent object is updated within the same transaction where we create and store its child audit record - complete with Envers revision. We can get the latest revision:
Number revision = reader.getRevisionNumberForDate(new Date(Long.MAX_VALUE));
or create a new revision:
Number revision = reader.getCurrentRevision(DefaultRevisionEntity.class, true).getId();
and use either, but the commit of the parent always happens after that. And that is when Envers increments the revision. Hence the revision we actually need to reference in the audit record is always higher than the value stored. In the simplest case, we get and store revision N but the parent version we need is stored as N + 1.
The AuditReader reader reference can be obtained with:
JpaTransactionManager transactionManager; // injected
EntityManagerFactory emf = transactionManager.getEntityManagerFactory();
EntityManager entityManager = emf.createEntityManager();
AuditReader reader = AuditReaderFactory.get(entityManager);
We're using Spring 3 @Transactional annotations and Hibernate 4.2.
Minimal class graph:
Parent.class
int version // for hibernate optimistic locking
String revisionName
List<AuditChild> audits
AuditChild.class
int enversRevision // use to retrieve previous graphs of parent
I've tried numerous approaches to force the commit of parent to occur first, among them:
Everything I've tried has either had no effect or caused other problems. I'd be happy to hear of solutions which have worked for others. Thanks.
Upvotes: 8
Views: 4492
Reputation: 21133
An easier alternative would be to mak use of @Version
.
First, Envers needs to be configured to actually audit the optimistic locking field's value, which it doesn't do by default. You do this by setting the following configuration:
org.hibernate.envers.do_not_audit_optimistic_locking_field=false
At this point, Envers will include the field in the audit history table and will replicate the assigned ORM version value to the audit history table. At this point, we have a unique way to join the ORM entity row with the audit history row using @Formula
. The formula SQL would be:
SELECT e.REV
FROM YourEntity_AUD e
WHERE e.originalId.id = id
AND e.version = version
Now its just as simple as adding a field to your entity:
@Formula( /* the SQL from above */)
@NotAudited
private Integer revisionNumber;
HTH.
Upvotes: 1