Main Tutorials

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 Author

author image
Founder of Mkyong.com, love Java and open source stuff. Follow him on Twitter. If you like my tutorials, consider make a donation to these charities.

Comments

Subscribe
Notify of
40 Comments
Most Voted
Newest Oldest
Inline Feedbacks
View all comments
Ayaz
5 years ago

Hello,
Just to inform that ‘postFlush’ is not triggered after committing to database. It is triggered when the session flushed. If a rollback occurs postFlush will be triggered so the comment should be changed 🙂

Thanks for the great post.

Pal
5 years ago

Is there a way to get the transient field value in the interceptor

Jedaro
8 years ago

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
8 years ago

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

Heileen Goodson
8 years ago

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
8 years ago

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
8 years ago

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
8 years ago

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
8 years ago
Reply to  latcha

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

Nikkie
9 years ago

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
9 years ago

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

Zahid
9 years ago

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
9 years ago
Reply to  Zahid

Using Hibernate 4.2.3.Final

Anand
9 years ago

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
postFlush
postFlush – insert
Hibernate:
insert
into
mkyong.auditlog
(ACTION, CREATED_DATE, DETAIL, ENTITY_ID, ENTITY_NAME)
values
(?, ?, ?, ?, ?)
preFlush
Hibernate:
select
stock0_.STOCK_ID as STOCK1_0_,
stock0_.STOCK_CODE as STOCK2_0_,
stock0_.STOCK_NAME as STOCK3_0_
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
(?, ?, ?, ?, ?)

Anand
9 years ago
Reply to  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
9 years ago

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
7 years ago
Reply to  bhuvan

Am getting the following exception : java.util.ConcurrentModificationException

When multiple tx are in process.

Can you please help

Mahi
10 years ago

Not able to build this project 🙁
Please help

soliddevv
10 years ago

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

Heileen Goodson
8 years ago
Reply to  soliddevv

you find the solution at that problem?

Maxim
10 years ago

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
10 years ago
Reply to  Maxim

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
11 years ago

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

Mark
10 years ago
Reply to  viniston

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

Mark.

ravi
11 years ago

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
11 years ago
Reply to  ravi

_ Sorry, Pls ignore my prev post

Sandeep Natoo
11 years ago

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
11 years ago

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
11 years ago

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
12 years ago

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
12 years ago

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

Arby
12 years ago
Reply to  mkyong

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
11 years ago
Reply to  Arby

@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
13 years ago

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

get online
6 years ago
Reply to  mkyong

how a marker interface will have operations ? The interface you have created can not be marker interfce .

Vivek karemore
6 years ago
Reply to  get online

Ignore Marker keyword here.. it is “Market” interface

pihentagy
14 years ago

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.

Mark
14 years ago

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