Hibernate Envers

Data auditing plays a very important role in tracking end-users data modifications and helps in reverting back unwanted changes and also get complete user modified history.

To achieve audit functionality via ORM framework, Envers (Entity Versioning) is a simple yet elegant and powerful library that facilitates developers to implement audit functionality within ORM framework transparent from database vendor.

Actually we can also achieve same functionality by

  • Creating triggers at Database level
  • Using Hibernate Interceptors
  • Using Hibernate Event listeners
  • Envers
Now we discuss more clearly on Envers

Why Envers:

Actually, audit functionality can be done succesfully by employing Hibernate Interceptor or Event listener approaches, so why use to Envers. Using Envers, to audit any entity, no extra actions are needed for the programmer to write historical data, it is done explicitly behind the scenes. See some of the benefits from Envers.

  • Easy Implementation: If we want Audit any Entity, we just need to add @Audited annotation to entities.
  • Versioning: This is similar to Subversion. Using Envers we can achieve same revision number for all related entities of one particular transaction. With other Audit solution it might be error prone, time consuming and extra care should be taken.
  • Vendor Independent: By default we become Data Base Independent for auditing solution. No need to bother of underlying Database vendor.
  • Key Feature: One of most essential feature of Envers is that isolated from versioning basic data types, like strings, numbers, dates, etc, we can also version relations between entities and Envers supports all mappings defined by the JPA specification.
  • Productivity: If we go with Envers, We can reduce following and increase productivity.

Less implementation time, so we can save lot of time.

No need to spend lot of time in testing.

We can reduce maintenance cost.

  • Querying or Retrieval:Envers is a very dynamic nature for reverting back entities data because,Straightforward history reading The queries in Envers are similar to Hibernate paradigm.
  • It is completely transparent to the programmer.
  • Support: It is community driven project so you will get regular updates and bug fixes at no cost. Envers upcoming feature: Present Enver is a portion of hibernate core and in future it would be issued ass a module in hybernate 3.5 release.
How Envers works:
  • Configure Envers. Refer to ‘Envers Configuration’
  • Specify @Audited for entities that should be audited
  • For each audited entity, an audit table is (dynamically) created E.g. entity ‘Address’ has an ‘Address_AUD’
  • On an update/insert/delete: data inserted to audit tables.
  • All changes in a transaction are stored under 1 revision number.
  • Revisions are global like SVN.
  • And finally, Query on audited table to retrieve data.

Envers Configuration:

Configuring Envers is very easy and straight forward. You configure Envers in hibernate.cfg.xml or in persistence.xml if you are using JPA. You can also configure directly into in hibernare configuration class “AnnotationConfiguration”.

Configuration in hibernate.cfg.xml or in persistence.xml:
						
<property name="hibernate.ejb.event.post-insert" value="org.hibernate.ejb.event.EJB3PostInsertEventListener, org.hibernate.enves.event.AuditEventListener" / > <property name="hibernate.ejb.event.post-update" value="org.hibernate.ejb.event.EJB3PostUpdateEventListener, org.hibernate.enves.event.AuditEventListener" /> <property name="hibernate.ejb.event.post-delete" value="org.hibernate.ejb.event.EJB3PostDeleteEventListener, org.hibernate.enves.event.AuditEventListener" /> <property name="hibernate.ejb.event.pre-collection-update" value="org.hibernate.envers.event.AuditEventListener" /> <property name="hibernate.ejb.event.pre-collection-remove" value="org.hibernate.envers.event.AuditEventListener" /> <property name="hibernate.ejb.event.post-collection-recreate" value="org.hibernate.envers.event.AuditEventListener" />

Configuration in hibernate.properties:

						
hibernate.ejb.event.post-insert= org.hibernate.envers.event.AuditEventListener hibernate.ejb.event.post-update= org.hibernate.envers.event.AuditEventListener hibernate.ejb.event.post-delete= org.hibernate.envers.event.AuditEventListener hibernate.ejb.event.pre-collection-update= org.hibernate.envers.event.AuditEventListener hibernate.ejb.event.pre-collection-remove= org.hibernate.envers.event.AuditEventListener hibernate.ejb.event.post-collection-recreate= org.hibernate.envers.event.AuditEventListener hibernate.hbm2ddl.auto=update

Configure in Hibernate AnnotationConfiguration class:

						
AnnotationConfiguration annotationConfiguration = new AnnotationConfiguration().configure(); annotationConfiguration.setProperty(“hibernate.hbm2ddl.auto“, “update”); AuditEventListener[] auditEventListener = new AuditEventListener[] {new AuditEventListener()}; annotationConfiguration.getEventListeners(). setPostInsertEventListeners(auditEventListener); annotationConfiguration.getEventListeners(). setPostUpdateEventListeners(auditEventListener); annotationConfiguration.getEventListeners(). setPostDeleteEventListeners(auditEventListener); annotationConfiguration.getEventListeners(). setPreCollectionUpdateEventListeners(auditEventListener); annotationConfiguration.getEventListeners(). setPreCollectionRemoveEventListeners(auditEventListener); annotationConfiguration.getEventListeners(). setPostCollectionRecreateEventListeners(auditEventListener);

Customizations or Extra options possible with Envers: check the following possible Extra options or Customization with Envers.

  1. By default, the audit table name is created by adding a “_AUD” suffix to the original entity name, but this can be overridden by specifying a different suffix/prefix.
  2. This is possible at Entity level and also at Configuration level.
  3. If you don’t want to Audit any entity relation within a domain you can annotate that entity relation as a @NotAudited . When @NotAudited is employed to a field, represents that this field should not be audited.
  4. If you want to Audit only specific fields, then you can specify span id="highlight">@Audited for fields rather than for Entity. When employed to a class, shows that all of its properties should be audited. @Audited, when we applied to a field, that indicates field should be audited.
  5. org.hibernate.envers.auditTablePrefix or org.hibernate.envers.auditTableSuffix: This two properties can used to keep prefix or siffix for Audit table. Example AUD_Audit or Audit_AUD.
  6. org.hibernate.envers.revisionOnCollectionChange: if this is set to true, then Should a revision be created when a not-accesed relation field modifies (it could be either a collection in a one-to-many relation, or the field with “mappedBy” attribute in a one-to-one relation).
  7. org.hibernate.envers.doNotAuditOptimisticLockingField: if this set to true:properties which are used for optimistic locking, annotated with @version, would be automatically does nit audited (their history will not hold; it does not make able to hold it).
Test Result: We have tested Envers for the following hibernate mappings.
Assume all example mappings shown below are defined under domain ‘Employee’ .
						
Case1: OneToMany @Cascade(org.hibernate.annotations.CascadeType.ALL) @JoinColumn(name = "userId") @OneToMany(cascade = CascadeType.ALL, mappedBy = "m_userInformation") private List <Address> m_address = new ArrayList<Address>(); Case2: ManyToMany mapping with List @Cascade(org.hibernate.annotations.CascadeType .DELETE_ORPHAN) @JoinTable(name = "Map_UserInfo_Cus", joinColumns = @JoinColumn(name = "UserID") inverseJoinColumns = @JoinColumn(name = "CusId")) @ManyToMany(cascade = CascadeType.ALL) private List<Customer> m_customer = new ArrayList<Customer>(); Case3: ManyToMany mapping with Map @Cascade(org.hibernate.annotations .CascadeType.DELETE_ORPHAN) @JoinTable(name = "EmployeeCAVal", joinColumns = @JoinColumn(name = "ID"), inverseJoinColumns = @JoinColumn(name = "CAValID")) @ManyToMany(cascade = CascadeType.ALL) @MapKeyManyToMany(joinColumns = @JoinColumn(name = "CADefID"))
						
private Map<CADefefinition, CAValue> m_customAttributeValues = new HashMap<CADefefinition, CAValue>();

Now, when you run any DB operation on Employee domain,

then depending on change in state of the related entities of Employee (i.e. above 3 cases),

Envers will insert audit into the following Audit tables.

 
private MapEmployee_AUD Address_AUD // Case 1 // Audited only If there is change in state of an 'Address'. Employee_Address_AUD // Case 1 Customer_AUD // Case 2 // Audited only If there is change in state of an 'Customer'. CADefefinition_AUD // Case 3 CAValue_AUD // Case 3 EmployeeCAVal_AUD // Case 3
Querying or retrieval of Audited Data:

Envers provides a most powerful AuditReader interface is confess the developers to travel back it in time and also transport entity state from audit tables at the certain point in time by specifying criteria. It is a very powerful nature for retriving the back entities data.we can use the Envers it is possible to prospect the data that the database contained at a given timestamp/revision with slowly.

If you are familiar with Hibernate Criteria , then it is very easy to write queries in Envers.

Using Envers it is possible to prospect the data that the database ac at accomidated at given timestamp/revision with slowly.

we can think of Audited data as two dimensions.

Horizontal: is the state of particular row in a table at a given revision. So that we can query for as those are included part of revision N.

Vertical: These are the revisions where variations occured in entities. Here by we can query for revisions, in which a given entity modified.

To write Audit query, first we need create instance of AuditReader. You can create instance in the following way

						
protected static AuditReader getAuditReader(SessionFactory sf) { return AuditReaderFactory.get(sf.openSession()); }
Once you have AuditReader instance, it’s very easy write queries, see some of the querying examples and options available in Envers AuditReader. AuditReader auditReader = getAuditReader(session);// get AuditReader
						
-> Horizontal Querying. // Below code snippet Creates Query and will return list of employee objects at revision number 9 AuditQuery query = auditReader.createQuery(). forEntitiesAtRevision(Employee.class, 9); List<Employee> auditResult = query.getResultList(); // Get employee objects at revision number 9. // You can write equal expression as query.add(AuditEntity.property("name").eq("John"));
// Even You can write equal expression on entities like query.add(AuditEntity.property(“address”).eq(addressEntityInstance)); // or query.add(AuditEntity.relatedId(“address”).eq(addressEntityId)); -> Vertical Querying // Below code creates query to retrieve particular Revision number of an entity

Number revision = (Number ) getAuditReader().createQuery() .forRevisionsOfEntity(Employee.class, false, true)// -> Vertical Querying. For more information you can refer to http://jboss.org/files/envers/docs/index.html#queries

Disadvantages:

Only disadvantage using Envers, it is creating one more extra table for each table. We can solve this problem to some extent by doing following

Only disadvantage using Envers, it is creating one more extra table for each table. We can solve this problem to some extent by doing following

While development time, to make clear separation between application tables and Audit table, we can keep prefix as ‘Z_’ to Audit tables so that they come in the end.

And also now a day’s no one is bothering about space, so creating multiple tables is not an issue.Even if you feel you’re not interested in Auditing complete domain, then you can Audit only required attributes on a domain.We can minimize the usage space.

To maintain same revision number for one transaction and for easy to history reading, Envers is maintaining all history information in separate table, so may be in this perspective this as an advantage.

Conclusion: Now a day’s no one is really interested to spend lot of time in development, so obviously no one will spend much time in Auditing, because most of the time Auditing will be the extra feature but not an important functionality for most of the Applications or Products. And also If we develop Auditing solution by our self, there is more chance of error prone, time consuming and maintenance cost, so why can’t we use this community driven project and we will also get regular updates and bug fixes at no cost.
HTML Comment Box is loading comments...