Hibernate Envers is a library that helps us to easily achieve audit functionality. It aims to enable easy auditing/versioning of persistent classes/entities. It provides a built in mechanism to maintain history of tables in Hibernate. Hibernate Envers has been created by Adam Warski. From Hibernate 3.5, Envers is part of core module.
Envers works in a similar fashion to Version Control Systems (like Subversion/CVS etc.) which maintains Revisions for changes. In Envers, Every Transaction is a Revision (unless the transaction didn’t modify any audited entity). Revision has the same concept as “Change Set in Version Control Systems”.
also read:
Using Revision Number, you can fetch various details like:
- Revision commit time stamp
- Query for various entities at that revision
Hibernate Envers Features
- Auditing of all mappings defined by JPA specification or Hibernate mappings (which extend JPA)
- Querying historical data
(source : SlideShare)
Environment Setup
- Add hibernate-envers jar on class path
- Add Hibernate Annotations or Entity Manager jars on class path
- Entities must have immutable unique identifiers (primary keys).
- Download the source code for finding the maven dependencies required for running a simple example
How Envers Works?
- Annotate your persistent class or some of its properties that you want to audit with @Audited.
- If you are using Old Versions of Hibernate then you may need to specify listeners (mentioned below) in Hibernate Configuration file. If you are using latest versions of Hibernate then just add Hibernate-Envers jar file in the classpath, it automatically register listeners.
<listener class="org.hibernate.envers.event.AuditEventListener" type="post-insert"/> <listener class="org.hibernate.envers.event.AuditEventListener" type="post-update"/> <listener class="org.hibernate.envers.event.AuditEventListener" type="post-delete"/> <listener class="org.hibernate.envers.event.AuditEventListener" type="pre-collection-update"/> <listener class="org.hibernate.envers.event.AuditEventListener" type="pre-collection-remove"/> <listener class="org.hibernate.envers.event.AuditEventListener" type="post-collection-recreate"/>
- For each audited entity, a new table will be created “entity_table_AUD”, which will hold the history of changes made to the entity.
- Hibernate automatically creates Audit tables if below property is set to “create, create-drop or update” in hibernate configuration file.
<property name="hbm2ddl.auto">update</property>
- Alternatively you can also create DDL statements using Ant Tasks or manually.
- You can then retrieve and query historical data without much effort. The audit (history) of an entity can be accessed using the AuditReader interface, which can be obtained having an open EntityManager or Session via the AuditReaderFactory.
Hibernate Envers Example
hibernate.cfg.xml:
<?xml version="1.0" encoding="utf-8"?> <!DOCTYPE hibernate-configuration PUBLIC "-//Hibernate/Hibernate Configuration DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd"> <hibernate-configuration> <session-factory> <!-- DB Connection Details --> <property name="connection.url">jdbc:mysql://localhost:3306/testdb</property> <property name="connection.username">root</property> <property name="connection.password">root</property> <property name="connection.driver_class">com.mysql.jdbc.Driver</property> <property name="dialect">org.hibernate.dialect.MySQLDialect</property> <!-- Logger Details : to show queries on console--> <property name="show_sql">true</property> <property name="format_sql">true</property> <!-- Envers automatically creates audit tables if below property is set to create, create-drop or update --> <property name="hbm2ddl.auto">update</property> <property name="connection.pool_size">1</property> <property name="current_session_context_class">thread</property> <!-- Entity mapping --> <mapping class="com.domain.Article" /> </session-factory> </hibernate-configuration>
Above hibernate.cfg.xml file is simple enough to understand, below is one important property from above xml:
<property name="hbm2ddl.auto">update</property>
Above property is required for automatic “Audit Table Creation”, alternatively you can also create tables manually (follow the Audit Table format).
Hibernate Envers Example
I have created a simple example that illustrates how hibernate envers stores the revision data in different scenarios. This example has the following components. At the end of this tutorial, you can find a download link for downloading the source code for this example.
- HibernateUtil.java – Creating session factory
- Example 1 – Using @Audited at class level
- Example 2 – Using @Audited at class level and @NotAudited for properties
- Example 3 – Using @Audited at property level
HibernateUtil.java
It is just a Utility class for building Hibernate Session Factory.
package com.util; import org.hibernate.SessionFactory; import org.hibernate.cfg.Configuration; public class HibernateUtil { private static final SessionFactory sessionFactory = buildSessionFactory(); private static SessionFactory buildSessionFactory() { try { return new Configuration().configure().buildSessionFactory(); } catch (Exception ex) { System.err.println("Build SessionFactory Failed:" + ex); throw new ExceptionInInitializerError(ex); } } public static SessionFactory getSessionFactory() { return sessionFactory; } public static void shutdown() { getSessionFactory().close(); } }
Example 1
package com.domain; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.Id; import javax.persistence.Table; import org.hibernate.envers.Audited; @Entity @Table(name = "article") @Audited public class Article { @Id @GeneratedValue @Column(name = "id") private int id; @Column(name = "subject") private String subject; @Column(name = "content") private String content; @Column(name = "author") private String author; public int getId() { return id; } public void setId(int id) { this.id = id; } public String getSubject() { return subject; } public void setSubject(String subject) { this.subject = subject; } public String getContent() { return content; } public void setContent(String content) { this.content = content; } public String getAuthor() { return author; } public void setAuthor(String author) { this.author = author; } }
In Above Entity, you can see “@Audited” is used at class level, means all property of the class will be involved in Auditing.
Test Class:
package com.test; import java.util.List; import org.hibernate.Session; import org.hibernate.envers.AuditReader; import org.hibernate.envers.AuditReaderFactory; import com.domain.Article; import com.util.HibernateUtil; public class TestArticle { public static void main(String[] args) { TestArticle testArticle = new TestArticle(); // creating two articles Article firstArticle = getFirstArticle(createArticle()); Article secondArticle = getSecondArticle(createArticle()); // saving both articles testArticle.saveArticle(firstArticle); // Transaction 1 testArticle.saveArticle(secondArticle); // Transaction 2 // updating first article firstArticle.setContent("Test Content2"); testArticle.saveOrUpdateArticle(firstArticle); // Transaction 3 // deleting second article testArticle.deleteArticle(secondArticle); // Transaction 4 AuditReader reader = AuditReaderFactory.get(HibernateUtil.getSessionFactory().openSession()); // List all revisions of an entity List<Number> versions = reader.getRevisions(Article.class, new Integer(2)); for (Number number : versions) { System.out.print(number + " "); } // Retrieve an object in a previous version Article firstArticleFromDB = (Article) reader.find(Article.class, new Integer(1), 2); System.out.println("Output 1: " + firstArticleFromDB.getId() + " " + firstArticleFromDB.getSubject() + " " +firstArticleFromDB.getContent() + " " + firstArticleFromDB.getAuthor()); } private static Article createArticle() { return new Article(); } private static Article getFirstArticle(Article firstArticle) { firstArticle.setSubject("First Article : Hibernate Envers"); firstArticle.setContent("Test Content"); firstArticle.setAuthor("Author1"); return firstArticle; } private static Article getSecondArticle(Article secondArticle) { secondArticle.setSubject("Second Article : Hibernate Envers II"); secondArticle.setContent("Test Content"); secondArticle.setAuthor("Author1"); return secondArticle; } public void saveArticle(Article article) { Session session = HibernateUtil.getSessionFactory().openSession(); session.beginTransaction(); session.save(article); session.getTransaction().commit(); } public void saveOrUpdateArticle(Article article) { Session session = HibernateUtil.getSessionFactory().openSession(); session.beginTransaction(); session.saveOrUpdate(article); session.getTransaction().commit(); } public void deleteArticle(Article article) { Session session = HibernateUtil.getSessionFactory().openSession(); session.beginTransaction(); session.delete(article); session.getTransaction().commit(); } }
In Above Test Class, we are performing 4 transactions:
- Transaction 1 : Saving “firstArticle”
- Transaction 2 : Saving “secondArticle”
- Transaction 3 : Updating “firstArticle”
- Transaction 4 : Deleting “secondArticle”
When we run above Test Program, two tables were created (apart from ‘article’ table, which is domain table):
- article_aud : This will contain history (modification/creation/deletion) of our entities. The primary key is composed of ID and REV fields.
you can see 4 revisions in article_aud table:
Since we added @Audited annotation at class level, all property of the class are available in Audit Table.
Apart from that We are having two extra columns in Audit Table:
- REV: Revision Number, which maintains same sequence order as you performed Transactions.
- REVTYPE: It defines type of Transaction whether it is Save/Update/Delete
- 0 means creation
- 1 means modification
- 2 means deletion
revinfo : Contains revision time stamp
Time stamp is in “Long” format. You can easily convert it to Human readable format (Sample Code is below):
long val = 1429237881803l; Date date=new Date(val); SimpleDateFormat df2 = new SimpleDateFormat("dd/MM/yy"); String dateText = df2.format(date); System.out.println(dateText);
Test program also contains some code for:
- Listing all revisions of an entity
- Also retrieving an object at specific revision
We are using ” AuditReader” API for querying details from Audit Tables.
Example 2 :
Now I am changing my domain class a little, still @Audited annotation is used at class level, but I have added @NotAudited for property “Author”.
package com.domain; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.Id; import javax.persistence.Table; import org.hibernate.envers.Audited; import org.hibernate.envers.NotAudited; @Entity @Table(name = "article") @Audited public class Article { @Id @GeneratedValue @Column(name = "id") private int id; @Column(name = "subject") private String subject; @Column(name = "content") private String content; @Column(name = "author") @NotAudited private String author; public int getId() { return id; } public void setId(int id) { this.id = id; } public String getSubject() { return subject; } public void setSubject(String subject) { this.subject = subject; } public String getContent() { return content; } public void setContent(String content) { this.content = content; } public String getAuthor() { return author; } public void setAuthor(String author) { this.author = author; } }
When we run Test program & check “article_aud” table, we can see below output:
You can notice there is no “author” column on Audit table now.
Example 3:
Now I am changing my domain class again and instead using “@NotAudited” annotation, I am using “@Audited” annotation at property level (I removed @Audited annotation from class level).
package com.domain; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.Id; import javax.persistence.Table; import org.hibernate.envers.Audited; @Entity @Table(name = "article") public class Article { @Id @GeneratedValue @Column(name = "id") private int id; @Column(name = "subject") @Audited private String subject; @Column(name = "content") private String content; @Column(name = "author") private String author; public int getId() { return id; } public void setId(int id) { this.id = id; } public String getSubject() { return subject; } public void setSubject(String subject) { this.subject = subject; } public String getContent() { return content; } public void setContent(String content) { this.content = content; } public String getAuthor() { return author; } public void setAuthor(String author) { this.author = author; } }
When you run the Test program, you can see below output on Audit table:
You can see, Audit table contains only “id” and “subject” from domain class.
Download SourceCode
[wpdm_file id=118 ]