Hibernate interceptor example – audit log

Hibernate has a powerful feature called ‘interceptor‘ to intercept or hook different kind of Hibernate events, like database CRUD operation. In this article, i will demonstrate how to implement an application audit log feature by using Hibernate interceptor, it will log all the Hibernate save, update or delete operations into a database table named ‘auditlog‘.

Hibernate interceptor example – audit log

1. Create a table

Create a table called ‘auditlog’ to store all the application audited records.

DROP TABLE IF EXISTS `mkyong`.`auditlog`;
CREATE TABLE  `mkyong`.`auditlog` (
  `AUDIT_LOG_ID` BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
  `ACTION` VARCHAR(100) NOT NULL,
  `DETAIL` text NOT NULL,
  `CREATED_DATE` DATE NOT NULL,
  `ENTITY_ID` BIGINT(20) UNSIGNED NOT NULL,
  `ENTITY_NAME` VARCHAR(255) NOT NULL,
  PRIMARY KEY (`AUDIT_LOG_ID`)
) ENGINE=InnoDB AUTO_INCREMENT=9 DEFAULT CHARSET=utf8;

2. Create a marker interface

Create a marker interface, any classes which implemented this interface will be audit. This interface requires that the implemented class to expose it identifier – getId() and the content to log – ‘getLogDeatil()‘. All exposed data will be store into database.

package com.mkyong.interceptor;
//market interface
public interface IAuditLog {
 
	public Long getId();	
	public String getLogDeatil();
}

3. Map the ‘auditlog’ table

A normal annotation model file to map with table ‘auditlog’.

@Entity
@Table(name = "auditlog", catalog = "mkyong")
public class AuditLog implements java.io.Serializable {
 
	private Long auditLogId;
	private String action;
	private String detail;
	private Date createdDate;
	private long entityId;
	private String entityName;
        ...
}

4. A class implemented the IAuditLog

A normal annotation model file to map with table ‘stock’, which will use for interceptor demo later. It have to implemented the IAuditLog marker interface and implement the getId() and getLogDeatil() method.

...
@Entity
@Table(name = "stock", catalog = "mkyong"
public class Stock implements java.io.Serializable, IAuditLog {
...
        @Transient
	@Override
	public Long getId(){
		return this.stockId.longValue();
	}
 
	@Transient
	@Override
	public String getLogDeatil(){
		StringBuilder sb = new StringBuilder();
		sb.append(" Stock Id : ").append(stockId)
		.append(" Stock Code : ").append(stockCode)
		.append(" Stock Name : ").append(stockName);
 
		return sb.toString();
	}
...

5. Create a Helper class

A helper class to accept the data from interceptor and store it into database.

...
public class AuditLogUtil{
 
   public static void LogIt(String action,
     IAuditLog entity, Connection conn ){
 
     Session tempSession = HibernateUtil.getSessionFactory().openSession(conn);
 
     try {
 
	AuditLog auditRecord = new AuditLog(action,entity.getLogDeatil()
		, new Date(),entity.getId(), entity.getClass().toString());
	tempSession.save(auditRecord);
	tempSession.flush();
 
     } finally {	
	tempSession.close();		
     }		
  }
}

6. Create a Hibernate interceptor class

Create a interceptor class by extends the Hibernate EmptyInterceptor. Here is the most popular interceptor function.

  • onSave – Called when you save an object, the object is not save into database yet.
  • onFlushDirty – Called when you update an object, the object is not update into database yet.
  • onDelete – Called when you delete an object, the object is not delete into database yet.
  • preFlush – Called before the saved, updated or deleted objects are committed to database (usually before postFlush).
  • postFlush – Called after the saved, updated or deleted objects are committed to database.

The code is quite verbose, it should self-exploratory.

...
public class AuditLogInterceptor extends EmptyInterceptor{
 
	Session session;
	private Set inserts = new HashSet();
	private Set updates = new HashSet();
	private Set deletes = new HashSet();
 
	public void setSession(Session session) {
		this.session=session;
	}
 
	public boolean onSave(Object entity,Serializable id,
		Object[] state,String[] propertyNames,Type[] types)
		throws CallbackException {
 
		System.out.println("onSave");
 
		if (entity instanceof IAuditLog){
			inserts.add(entity);
		}
		return false;
 
	}
 
	public boolean onFlushDirty(Object entity,Serializable id,
		Object[] currentState,Object[] previousState,
		String[] propertyNames,Type[] types)
		throws CallbackException {
 
		System.out.println("onFlushDirty");
 
		if (entity instanceof IAuditLog){
			updates.add(entity);
		}
		return false;
 
	}
 
	public void onDelete(Object entity, Serializable id, 
		Object[] state, String[] propertyNames, 
		Type[] types) {
 
		System.out.println("onDelete");
 
		if (entity instanceof IAuditLog){
			deletes.add(entity);
		}
	}
 
	//called before commit into database
	public void preFlush(Iterator iterator) {
		System.out.println("preFlush");
	}	
 
	//called after committed into database
	public void postFlush(Iterator iterator) {
		System.out.println("postFlush");
 
	try{
 
		for (Iterator it = inserts.iterator(); it.hasNext();) {
		    IAuditLog entity = (IAuditLog) it.next();
		    System.out.println("postFlush - insert");		
		    AuditLogUtil.LogIt("Saved",entity, session.connection());
		}	
 
		for (Iterator it = updates.iterator(); it.hasNext();) {
		    IAuditLog entity = (IAuditLog) it.next();
		    System.out.println("postFlush - update");
		    AuditLogUtil.LogIt("Updated",entity, session.connection());
		}	
 
		for (Iterator it = deletes.iterator(); it.hasNext();) {
		    IAuditLog entity = (IAuditLog) it.next();
		    System.out.println("postFlush - delete");
		    AuditLogUtil.LogIt("Deleted",entity, session.connection());
		}	
 
	} finally {
		inserts.clear();
		updates.clear();
		deletes.clear();
	}
       }		
}

7.Enabling the interceptor

You can enable the interceptor by pass it as an argument to openSession(interceptor);.

...
   Session session = null;
   Transaction tx = null;
   try {
 
	AuditLogInterceptor interceptor = new AuditLogInterceptor();
	session = HibernateUtil.getSessionFactory().openSession(interceptor);
	interceptor.setSession(session);
 
	//test insert
	tx = session.beginTransaction();
	Stock stockInsert = new Stock();
	stockInsert.setStockCode("1111");
	stockInsert.setStockName("mkyong");
	session.saveOrUpdate(stockInsert);
	tx.commit();
 
	//test update
	tx = session.beginTransaction();
	Query query = session.createQuery("from Stock where stockCode = '1111'");
	Stock stockUpdate = (Stock)query.list().get(0);
	stockUpdate.setStockName("mkyong-update");
	session.saveOrUpdate(stockUpdate);
	tx.commit();
 
	//test delete
	tx = session.beginTransaction();
	session.delete(stockUpdate);
	tx.commit();
 
   } catch (RuntimeException e) {
	try {
		tx.rollback();
	} catch (RuntimeException rbe) {
		// log.error("Couldn’t roll back transaction", rbe);
   }
	throw e;
   } finally {
	if (session != null) {
		session.close();
	}
   }
...

In insert test

session.saveOrUpdate(stockInsert); //it will call onSave
tx.commit(); // it will call preFlush follow by postFlush

In update test

session.saveOrUpdate(stockUpdate); //it will call onFlushDirty
tx.commit(); // it will call preFlush follow by postFlush

In delete test

session.delete(stockUpdate); //it will call onDelete
tx.commit();  // it will call preFlush follow by postFlush
Output
onSave
Hibernate: 
    insert into mkyong.stock
    (STOCK_CODE, STOCK_NAME) 
    values (?, ?)
preFlush
postFlush
postFlush - insert
Hibernate: 
    insert into mkyong.auditlog
    (ACTION, CREATED_DATE, DETAIL, ENTITY_ID, ENTITY_NAME) 
    values (?, ?, ?, ?, ?)
preFlush
Hibernate: 
    select ...
    from mkyong.stock stock0_ 
    where stock0_.STOCK_CODE='1111'
preFlush
onFlushDirty
Hibernate: 
    update mkyong.stock 
    set STOCK_CODE=?, STOCK_NAME=? 
    where STOCK_ID=?
postFlush
postFlush - update
Hibernate: 
    insert into mkyong.auditlog
    (ACTION, CREATED_DATE, DETAIL, ENTITY_ID, ENTITY_NAME) 
    values (?, ?, ?, ?, ?)
onDelete
preFlush
Hibernate: 
    delete from mkyong.stock where STOCK_ID=?
postFlush
postFlush - delete
Hibernate: 
    insert into mkyong.auditlog 
    (ACTION, CREATED_DATE, DETAIL, ENTITY_ID, ENTITY_NAME) 
    values (?, ?, ?, ?, ?)
In database
SELECT * FROM auditlog a;

All audited data are inserted into database.

interceptor-example

Conclusion

The audit logs is a useful feature that is often handled in database by using triggers, but i would recommend to use application to implement it for the portability concern.

Download this example – Hibernate interceptor example.zip
Tags :

About the Author

mkyong
Founder of Mkyong.com and HostingCompass.com, love Java and open source stuff. Follow him on Twitter, or befriend him on Facebook or Google Plus. If you like my tutorials, consider make a donation to these charities.

Comments

  • Pingback: water ionizer loan()

  • Pingback: YouTube likes kopen()

  • Pingback: penge laan nu()

  • Pingback: stop parking()

  • Pingback: car parking()

  • Pingback: Judith in self recorded bedroom solo()

  • Pingback: Blue Coaster33()

  • Pingback: us?ugi szklarskie piotrków()

  • soliddevv

    Hello,
    this does not work for me i am using hibbernate 4.1 can you help me thanks.

  • Maxim

    Does interceptor work globally or per session?

    I have this trigger:

    CREATE TRIGGER mytable_BI BEFORE INSERT ON mytable
    FOR EACH ROW BEGIN
    SET NEW.rank = (SELECT IFNULL(MAX(rank),-1) + 1 FROM channelGroupItem WHERE parentId = NEW.parentId);
    END;

    I’ve tried to use interceptor but I need a session inside an interceptor to check the maximum value in the DB.

    • Lucky

      Same problem here…did you get an answer?
      I need to invoke StoredProc from interceptor onSave and postFlush.
      I am using spring configuration – AnnotationSessionFactoryBean for session factory.
      something like this….

      Not able to bind sessionFactory or session back to auditInterceptor.

  • viniston

    can you pls guide me implement this in C#.net pls…. Thank in advance….

  • ravi

    Hi, I am not much into hibernate but would like to know how different this is using AcpectJ/AOP to implement intercept functionality ? i think this can be easily achieved with aspects.

    • ravi

      _ Sorry, Pls ignore my prev post

  • Sandeep Natoo

    Nice Article MKYONG !!
    I have one query is it mandatory to use the same session for audit ?
    If yes, then if I configure interceptor through xml , how will I pass current session to interceptor.
    If you know then please tell us.

    Thanks !!

  • Dhruthi

    Hi mkyong,

    I have downloaded the above project file to my workplace. But in that i didn’t find the client program.
    Can you please tell me steps how i can run in my eclipse workplace.

    whether i have to use Junit for running this example?

    Thanks.

  • http://www.myspace.com/rbe-enterprises RBE ENTERPRISES

    I hardly write comments, but I browsed some responses on Spreading Holiday Cheer at Neiman Marcus. I do have 2 questions for you if it’s allright. Is it only me or do a few of these comments look as if they are written by brain dead individuals? And, if you are posting at other social sites, I’d like to follow you. Could you make a list of the complete urls of all your shared sites like your twitter feed, Facebook page or linkedin profile?

  • johndoe

    I tried it. The method onSave in the interceptor is called twice, and the main thread goes to AbstractSaveEventListener. The thread then keeps running infinitely.

  • Arby

    I’m sorry MyKong. I understand you are trying to teach people Hibernate and I am learning a lot very quickly. It surely beats trying to read 1000 page book. However as I opined once before this example might send wrong message to fresh developers.

    I shouldn’t be mangling my domain because I want to Audit something. I shouldn’t have to open my Stock class and implement IAuditLog. Interception should happen externally. Only thing I should do is perhaps create an @Auditable annotation on my domain classes. When application loads, it should simply make sure methods such as getId() and/or getLogDetail() are implemented.

    As was already pointed out in another reply, neither is this a marker interface, but more importantly I should not have to implement this interface at all. Furthermore, maybe I can simply use toString() instead of getLogDetail(). THis about this. Then I don’t even need getId(). IF my class uses getIdentity() instead it will still work.

    The whole point of interceptors is to keep class it is intercepting OBLIVIOUS. Class being intercepted should not know IF and WHY and HOW intercepting.

    Just my 2 cents

    • http://www.mkyong.com mkyong

      Thanks for your invaluable inputs and concerns. This article is demonstrated that Hibernate is able to handle interceptor task only.

      In production , Spring AOP is a better choice to act as the interceptor, and it matched what you mention above :)

      • Arby

        Yes, Spring interceptor is good choice. But if using Hibernate without Spring, I would like to make use of Hibernate interceptor. I think we can make it work without having Model class implement IAuditLog. I will think about this.

        • Abhi

          @Arby,
          Can you post example what you have thought of changing this example?
          I am beginner in hibernate and dont understand what u are talking about.By example it will be more clear.

          Thanks.

  • Hrishikesh

    Very Nice document…
    Just including the jars that will be needed for compilation/execution…

    * hibernate-annotations.jar
    * hibernate-core.jar
    * javax.persistence.jar
    * junit-3.8.1.jar
    * hibernate3.jar

    Regards
    Hrishikesh

    • http://www.mkyong.com mkyong

      Thanks for your tips, Maven will grab all those :)

  • Pingback: Hibernate Tutorials | Tutorials()

  • pihentagy

    Nice tutorial, just a small comment:
    “2. Create a marker interface”

    AFAIK Marker interface is the interface, which does not have any methods in it.
    So this is not a marker interface.

  • Pingback: Blog bookmarks 02/21/2010 « My Diigo bookmarks()

  • Mark

    Good info for how to use interceptors. But for auditing … Envers.