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.
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.
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.
Is there a way to get the transient field value in the interceptor
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);
}
Otherwise for activating interceptor, insert this line into persistence.xml
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
For Hibernate 4.5 that parameter in session = HibernateUtil.getSessionFactory().openSession(interceptor); is deprecated or not exist, how can y set de interceptor?
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
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
So sorry, but at the moment i cant find a solution
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..
hey mk test your damn app before you copy past from others
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.
Using Hibernate 4.2.3.Final
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
(?, ?, ?, ?, ?)
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.
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 ?
Am getting the following exception : java.util.ConcurrentModificationException
When multiple tx are in process.
Can you please help
Not able to build this project 🙁
Please help
Hello,
this does not work for me i am using hibbernate 4.1 can you help me thanks.
you find the solution at that problem?
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.
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.
can you pls guide me implement this in C#.net pls…. Thank in advance….
More about C# http://csharp.net-informations.com c# Tutorial
Mark.
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.
_ Sorry, Pls ignore my prev post
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 !!
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.
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?
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.
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
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 🙂
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.
@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.
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
Thanks for your tips, Maven will grab all those 🙂
how a marker interface will have operations ? The interface you have created can not be marker interfce .
Ignore Marker keyword here.. it is “Market” interface
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.
Good info for how to use interceptors. But for auditing … Envers.