Hibernate – Envers – Easy Auditing for Entity Classes

Posted: June 27, 2011 in ORM
Tags: , ,

Have you tracked your Entity Objects ? When it has created,modified and deleted with time.

Try Envers for Easy Auditing of Entity Classes. Very simple to audit your Entity classes using @Audited. Envers now becomes a part of Hibernate 3.5.

List of libraries you need for this

  • hibernate3.jar
  • antlr.jar
  • commons-collections.jar
  • dom4j-1.6.1.jar
  • javassist.jar
  • jpa-api-2.0-1.jar
  • jta.jar
  • mysql-connector-java-5.1.3-rc-bin.jar
  • slf4j-api-1.6.1.jar

1. hibernate.cfg.xml

Add the Audit Event Listeners in your 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>
<!-- Database connection settings -->
<property name="connection.driver_class">com.mysql.jdbc.Driver</property>
<property name="connection.url">jdbc:mysql://localhost:3306/envers</property>
<property name="connection.username">root</property>
<property name="connection.password">welcome123</property>
<!-- JDBC connection pool (use the built-in) -->
<property name="connection.pool_size">1</property>
<!-- SQL dialect -->
<property name="dialect">org.hibernate.dialect.MySQLDialect</property>
<!-- Enable Hibernate's automatic session context management -->
<property name="current_session_context_class">thread</property>
<!-- Disable the second-level cache -->
<property name="cache.provider_class">org.hibernate.cache.NoCacheProvider</property>
<!-- Echo all executed SQL to stdout -->
<property name="show_sql">true</property>
<!-- Drop and re-create the database schema on startup -->
<property name="hbm2ddl.auto">update</property>
<mapping/>
<!-- Hibernate ENVERS Listener Configuration -->
<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"/>
</session-factory>
</hibernate-configuration>


2.
Entity Class

Your Entity class should have @Audited for tracking the values in the persistent class.

package com;
import java.io.Serializable;
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;

/**
* @author Anand
*
*/
@Entity
@Table(name="user")
@Audited //---------------- This is more important!!!!!!!!
public class User implements Serializable {
@Id
@GeneratedValue
@Column(name="id")
private int id;
@Column(name="firstname")
private String firstname;
@Column(name="lastname")
private String lastname;
@Column(name="email")
private String email;
/**
* @return the email
*/
public String getEmail() {
return email;
}
/**
* @param email the email to set
*/
public void setEmail(String email) {
this.email = email;
}
/**
* @return the firstname
*/
public String getFirstname() {
return firstname;
}
/**
* @param firstname the firstname to set
*/
public void setFirstname(String firstname) {
this.firstname = firstname;
}
/**
* @return the id
*/
public int getId() {
return id;
}
/**
* @param id the id to set
*/
public void setId(int id) {
this.id = id;
}
/**
* @return the lastname
*/
public String getLastname() {
return lastname;
}
/**
* @param lastname the lastname to set
*/
public void setLastname(String lastname) {
this.lastname = lastname;
}
}

3. DB Structure of your Entity class

Field Type Null Key Default Extra
id int(11) NO PRI NULL auto_increment
firstname varchar(128) YES NULL
lastname varchar(128) YES NULL
email varchar(64) YES NULL

4. Insert the data
Now lets begin with Insert the data
Add the data to your Entity Class

User user = new User();
user.setFirstname("biju");
user.setLastname("cd");
user.setEmail("cdbiju@gmail.com");

Get the Session Factory and Session to save the data into DB

/** Getting the Session Factory and session */
//SessionFactory sessionfactory = HibernateUtil.getSessionFactory();
SessionFactory sessionfactory = new AnnotationConfiguration().configure().buildSessionFactory();
Session sess = sessionfactory.getCurrentSession();
/** Starting the Transaction */
Trans)action tx = sess.beginTransaction();
/** Saving POJO */
sess.save(user);
/** Commiting the changes */
tx.commit();
System.out.println("Record Inserted");
/** Closing Session */
sessionfactory.close();

5. New Tables Created for holding the Revision Entries

Table 1 : user_aud(Audit Table)

Audit table will have the default suffix to be _aud and present in the default schema of the database.
It has the same structure as the Entity table. Additionally it has three columns in it namely

  1. id
  2. REV
  3. REVINFO
Field Type Null Key Default Extra
id int(11) NO PRI NULL auto_increment
REV int(11) NO PRI NULL
REVTYPE tinyint(11) YES
firstname varchar(128) YES NULL
lastname varchar(128) YES NULL
email varchar(64) YES NULL

Table 2 : revinfo(common table for the Entity)

This revinfo table will be common for all the Entity classes. 
Field Type Null Key Default Extra
REV int(11) NO PRI NULL auto_increment
REVTSTMP bigint(20) YES
This two tables will be automatically created. 

6. Entries in the Audit table and Revinfo table after insert

id REV REVTYPE email firstname lastname
1 1 0 cdbiju@gmail.com biju cd

Here the id column is foreign key for the entity class “user”, REV will be primary key for the audited table. More Importantly the REVTYPE has three values in it.

0 = Creation

1 = Update

2 = Delete

Whenever the insertion takes for the entity class “user”,  it makes an entry as Zero and keeps all the  Audited columns values in it. (0 = Creation)

Look into Revinfo table

REV REVTSTMP
1 134343453534434

It contains the Revision Timestamp value.

7. Update the data and Look into Audit Tables

User user = new User();
user.setId(1);   // Passes the id=1 to the Entity Class
/** Getting the Session Factory and session */
SessionFactory sessionfactory = HibernateUtil.getSessionFactory();
Session sess = sessionfactory.getCurrentSession();
/** Starting the Transaction */
Transaction tx = sess.beginTransaction();
User u = (User) sess.get(User.class, user.getId());
u.setFirstname("biju-append");
u.setLastname("cd-append");
u.setEmail("cdbiju-append@gmail.com");
sess.saveOrUpdate(u);
/** Commiting the changes */
tx.commit();
System.out.println("Record Updated");
/** Closing Session */
sessionfactory.close();

Look into the Audit Table

id REV REVTYPE email firstname lastname
1 1 0 cdbiju@gmail.com biju Cd
1 2 1 cdbiju-append@gmail.com 
biju-append

 

cd-append

 

Note 1 is for Update (REVTYPE)

REV REVTSTMP
1 134343453534434
2 134343453534434

When we update the same record, it updates the Audited columns values and update the REVTYPE to be 1.( 1 = Updation)

8. Delete the data and Look into Audit Tables

User user = new User();
user.setId(1);   // Passes the id=1 to the Entity Class to delete
/** Getting the Session Factory and session */
SessionFactory sessionfactory = HibernateUtil.getSessionFactory();
Session sess = sessionfactory.getCurrentSession();
/** Starting the Transaction */
Transaction tx = sess.beginTransaction();
User u = (User) sess.get(User.class, user.getId());
sess.delete(u);
/** Commiting the changes */
tx.commit();
System.out.println("Record Deleted");
/** Closing Session */
sessionfactory.close();

Look into the Audit Table

id REV REVTYPE email firstname lastname
1 1 0 cdbiju@gmail.com biju Cd
1 2 1 cdbiju-append@gmail.com 
biju-append

 

cd-append

 

1 3 2 NULL
NULL
NULL

Note 2 is for Delete (REVTYPE)

REV REVTSTMP
1 134343453534434
2 134343453534434
3 134343452343243

When the data is Deleted, the row has been deleted in the Entity table . But in the Audited Table, it updates all the Audited columns to be NULL and updates the REVTYPE to be 2.(2 = Delete)

Comments
  1. Thanks for the article Anand – sounds like a good idea. Have you any notion of the overhead? Is it possible to turn off auditing for non-necessary fields? Perhaps big text fields…

    Is this something coming though JPA or purely Hibernate?

    Rob
    🙂

    • Anand says:

      Thanks Robert. Yes it is possible to non audit some of the fields in the entity class by using the Annotation @Audited(targetAuditMode = RelationTargetAuditMode.NOT_AUDITED)..
      Now Envers has been added to the hibernate-core3.5.

  2. kpolo says:

    Interesting, but I have to say, not designed with a “production” app in mind. My argument:

    1) Dynamic generation of database tables is frowned upon (if not flat out disallowed) in most production database setups. In fact, the way we have things running, our apps connect with a different user that is not schema owner and our batch applications also connect with a different user (different from the app user). So this will not fly.

    2) For the audit table, is there any configuration on how many versions to retain or is that a manual task for the developer?

    3) What is most important in an audit table is who made the change and when. I find it puzzling that the revinfo table is common to all entities? How does one associate the rev with a particular entity? Is there an opportunity to add additional app specific metadata?

    4) How does the version attribute of a versioned entity fit with revisions?

  3. Java Pins says:

    Hibernate – Envers – Easy Auditing for Entity Classes « get2java…

    Thank you for submitting this cool story – Trackback from Java Pins…

Leave a comment