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

About the Author

author image
mkyong
Founder of Mkyong.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

Leave a Reply

avatar
newest oldest most voted
Jedaro
Guest
Jedaro

Hello! excelente post.

I have a problem:

tempSession.save(auditRecord);
tempSession.flush();

No save the object in database :( and I have inifinite loop in the next code:

for (Iterator it = updates.iterator(); it.hasNext();) {
IAuditLog entity = (IAuditLog) it.next();
System.out.println(“postFlush – update”);
AuditLogUtil.LogIt(“Updated”,entity);
}

Alessandro Mattiuzzi
Guest
Alessandro Mattiuzzi

Otherwise for activating interceptor, insert this line into persistence.xml

Heileen Goodson
Guest
Heileen Goodson

but i have many interceptors but i dont wanna conf in the xml. i wanna put in the injection of depencies, i cant find a solution for this

Heileen Goodson
Guest
Heileen Goodson

For Hibernate 4.5 that parameter in session = HibernateUtil.getSessionFactory().openSession(interceptor); is deprecated or not exist, how can y set de interceptor?

Heileen Goodson
Guest
Heileen Goodson

Hi! i try to the same but show me the next error:
The method openSession() in the type SessionFactory is not applicable for the arguments

I use hibernate 4.2.5, thanks for your help

latcha
Guest
latcha

Hi all ! I’m using hibernate 4.2.5 from hibernate 3.5.2 , i’m getting The method openSession() in the type SessionFactory is not applicable for the arguments…can you any one solve this problem…pls let me know resolution..im having same issue right now

Heileen Goodson
Guest
Heileen Goodson

So sorry, but at the moment i cant find a solution

Nikkie
Guest
Nikkie

In AuditLogInterceptor class. postFlush() method,

AuditLogUtil.LogIt(“Saved”,entity, session.connection());
AuditLogUtil.LogIt(“Updated”,entity, session.connection());
AuditLogUtil.LogIt(“Deleted”,entity, session.connection());

is not working. Can you help me please?
The error says that the method connection() is undefined for the type Session..

mk stupid
Guest
mk stupid

hey mk test your damn app before you copy past from others

Zahid
Guest
Zahid

Hi,
I have an entity with a parameter of type Set.
When the control comes to interceptor the value for this parameter comes to be null even if I have set the values for this parameter.
Could someone please help.

Zahid
Guest
Zahid

Using Hibernate 4.2.3.Final

Anand
Guest
Anand
Hello, Thanks for the application. I have a small concern when running the above code. When I right click on App.java file and say run as Java application it is printing below output in the console.But there are no new tables created such as stock_daily_record,category,auditlog,stock_category, stock,stock_daily_record,stock_detail. I only have one table in mkyong database that is customer.When I run the code it is supposed to generate the above mentioned tables automatically as we used annotations, but it is not. Can you let me know how to run the application. onSave Hibernate: insert into mkyong.stock (STOCK_CODE, STOCK_NAME) values (?, ?) preFlush… Read more »
Anand
Guest
Anand

Please ignore the above post. The issue is fixed. The tables got created successfully. I had another database with the similar name. Thanks for all the support.

bhuvan
Guest
bhuvan

Above code is subjected to a problem when there are more than one transaction are in process concurrently , and both transactions creates entities.

Let me take a example:

Tx_A create entity EA1, EA2.

Tx_B creates entity EB1, EB2.

Now let say execution flow happens this way:

[1] onSave for EA1, we add EA1 id to inserts set

[2] onSave for EB1, we add EB1 id to inserts set

[3] postFlush(tx) for Tx_A

Now at this point we will flush EA1 and EB1 id and now Tx_B rollbacks

we will log EB1 as created .. but it is not.

What can be the strategy to solve that ?

Sonu
Guest
Sonu

Am getting the following exception : java.util.ConcurrentModificationException

When multiple tx are in process.

Can you please help

Mahi
Guest
Mahi

Not able to build this project :(
Please help

soliddevv
Guest
soliddevv

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

Heileen Goodson
Guest
Heileen Goodson

you find the solution at that problem?

Maxim
Guest
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
Guest
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
Guest
viniston

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

Mark
Guest
Mark

More about C# http://csharp.net-informations.com c# Tutorial

Mark.

ravi
Guest
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
Guest
ravi

_ Sorry, Pls ignore my prev post

Sandeep Natoo
Guest
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
Guest
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.

RBE ENTERPRISES
Guest
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
Guest
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
Guest
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()… Read more »
Hrishikesh
Guest
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

trackback
Hibernate Tutorials | Tutorials

[…] Hibernate interceptor example – audit log Hibernate interceptor is used to intercept the Hibernate events like CRUD operations, a detail example of audit log implementation with Hibernate interceptor. […]

pihentagy
Guest
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.

trackback
Blog bookmarks 02/21/2010 « My Diigo bookmarks

[…] Hibernate interceptor example – audit log | Hibernate […]

Mark
Guest
Mark

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