View Full Version : Hibernate Interceptor and Audit Logging with Spring
Loumeister
Sep 24th, 2004, 12:57 PM
I want to create an audit log for any class that implements my Auditable interface. In Hibernate, you can write an Interceptor that extends their Interceptor interface to inject logging. Problem is that you need to attach the Interceptor to the session via "interceptor.setSession(session)" when the session is first opened. I'm using Spring for session management so I was wondering if I could somehow nest this Interceptor into a Spring interceptor and just use the application context to add it dynamically.
If there are other approaches to audit logging with Spring, I'd be interested to hear them.
Thanks,
Lou
Loumeister
Sep 24th, 2004, 03:00 PM
It looks like there's a setter for adding Hibernate Interceptors (getHibernateTemplate().setEntityInterceptor(inter ceptor)), but the apidoc states that it has to be on a *new* session. Since I'm telling Spring to use the Spring HibernateInterceptor, the session is automatically created for me. How can I add my AuditLog Interceptor to Spring's version before teh session is created? Is this something I could do in the applicationContext somehow?
I tried adding it my interceptor and using the HibernateInterceptor as the "parent" but that didn't work. I also tried adding it as its own interceptor and then adding it above the Hibernate interceptor in my list of interceptors that use it.
Here's my current save method:
public Serializable save(Object entity) {
Monitor mon = MonitorFactory.start(this.getClass().getName() + ":save");
try {
// Session session = SessionFactoryUtils.getSession(getSessionFactory() ,
// false);
AuditLogInterceptor interceptor = new AuditLogInterceptor();
// interceptor.setSession(session);
getHibernateTemplate().setEntityInterceptor(interc eptor);
return getHibernateTemplate().save(entity);
} finally {
mon.stop();
}
}
Here's my current applicationContext:
<bean id="myHibernateInterceptor"
class="org.springframework.orm.hibernate.HibernateInterce ptor">
<property name="sessionFactory">
<ref bean="mySessionFactory"/>
</property>
</bean>
<!-- AOP interceptor for trapping Spring runtime exceptions and to rethrow that
- as a checked user-defined exceptions. -->
<bean id="exceptionInterceptor" class="com.mitchell.services.technical.claim.exception.Ex ceptionInterceptor" />
<bean id="clmActivityLogDaoTarget"
class="com.mitchell.services.technical.claim.dao.spring.C lmActivityLogHibernateDao">
<property name="sessionFactory">
<ref local="mySessionFactory"/>
</property>
</bean>
<bean id="clmActivityLogDao"
class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="proxyInterfaces">
<value>com.mitchell.services.technical.claim.dao.ClmActiv ityLogDao</value>
</property>
<property name="interceptorNames">
<list>
<value>exceptionInterceptor</value>
<value>myHibernateInterceptor</value>
<value>clmActivityLogDaoTarget</value>
</list>
</property>
</bean>
Thanks,
Lou
irbouho
Sep 24th, 2004, 04:28 PM
Lou, you can configure your entityInterceptor in your applicationContext.xml. org.springframework.orm.hibernate.HibernateTransac tionManager has a property entityInterceptor that holds a Hibernate Entity Interceptor.
HTH
Loumeister
Sep 24th, 2004, 04:47 PM
I tried doing this, but it complains about the property setting. Is this documented somewhere? I couldn't find anything in the specification for entityInterceptor. Also, would this work if I'm using CMT?
Thanks again Omar,
Lou
<bean id="auditLogInterceptor" class="com.mitchell.services.technical.claim.util.AuditLo gInterceptor" />
<bean id="myTransactionManager"
class="org.springframework.orm.hibernate.HibernateTransac tionManager">
<property name="sessionFactory">
<ref local="mySessionFactory"/>
</property>
<property name="entityInterceptor">
<value>auditLogInterceptor</value>
</property>
</bean>
irbouho
Sep 24th, 2004, 04:55 PM
entityInterceptor is of type net.sf.hibernate.Interceptor, so you should use a <ref ...> here:
<bean id="auditLogInterceptor" class="com.mitchell.services.technical.claim.util.AuditLo gInterceptor" />
<bean id="myTransactionManager"
class="org.springframework.orm.hibernate.HibernateTransac tionManager">
<property name="sessionFactory">
<ref local="mySessionFactory"/>
</property>
<property name="entityInterceptor">
<ref local="auditLogInterceptor"/>
</property>
</bean>
HTH
Loumeister
Sep 24th, 2004, 08:33 PM
Ok, I was able to do this, but I had to use two SessionFactory's. At first, I thought I would be able to just create a new session, but since the entityInterceptor is registered with the SessionFactory, the postFlush() gets called again when I try to flush the log message. Thus causing an infinite loop. I really need to scope the entityInterceptor with the session...is there some way to accomplish this? I can't use the HibernateTransaction manager as you pointed out because I am using CMT.
Thanks,
Lou
Here's the way my application context ended up looking:
<bean id="mySessionFactory"
class="org.springframework.orm.hibernate.LocalSessionFact oryBean">
<property name="dataSource">
<ref local="clmDataSource"/>
</property>
<property name="configLocation">
<value>classpath:hibernate.cfg.xml</value>
</property>
<property name="entityInterceptor">
<ref local="auditLogInterceptor"/>
</property>
</bean>
<bean id="mySessionFactory2"
class="org.springframework.orm.hibernate.LocalSessionFact oryBean">
<property name="dataSource">
<ref local="clmDataSource"/>
</property>
<property name="configLocation">
<value>classpath:hibernate.cfg.xml</value>
</property>
</bean>
<bean id="auditLogInterceptor" class="com.mitchell.services.technical.claim.util.AuditLo gInterceptor" />
<bean id="auditLog"
class="com.mitchell.services.technical.claim.util.AuditLo g">
<property name="sessionFactory">
<ref local="mySessionFactory2"/>
</property>
</bean>
irbouho
Sep 25th, 2004, 09:35 PM
I can't use the HibernateTransaction manager as you pointed out because I am using CMT
EntityInterceptor can also be set at the HibernateTemplate and HibernateInterceptor levels.
but I had to use two SessionFactory's
AFAIK, an entityInterceptor should not manipulate the associated Hibernate Session. That is why you need to use two SessionFactories. You can also implement auditing using database triggers; did you consider this alternative?
Loumeister
Sep 26th, 2004, 01:21 PM
EntityInterceptor can also be set at the HibernateTemplate and HibernateInterceptor levels.
I tried experimenting at this level as well. If I set the entityInterceptor on anything but the SessionFactory, my interceptor does not get called.
AFAIK, an entityInterceptor should not manipulate the associated Hibernate Session. That is why you need to use two SessionFactories.
According to Hibernate in Action, you can just create a new Session based off the JDBC connection. I was thinking of using SessionFactoryUtils.getSession(getSessionFactory() , true), thereby with the "true" parameter a new session is forcibly created.
You can also implement auditing using database triggers; did you consider this alternative?
Yes, but the DBAs do not want to implement triggers. Trust me, I'd prefer doing this really. :D
Lou
irbouho
Sep 27th, 2004, 09:49 PM
I tried experimenting at this level as well. If I set the entityInterceptor on anything but the SessionFactory, my interceptor does not get called.
If a Hibernate Session is already bound to the thread, and has no entityInterceptor configured, applying your interceptor to HibernateTemplate will not work for you since SessionFactoryUtils always returns the pre-bound HibernateSession.
SessionFactoryUtils.getSession(getSessionFactory() , true), thereby with the "true" parameter a new session is forcibly created
SessionFactoryUtils.getSession returns the thread bound Hibernate Session if one is found, if no session is bound to the current thread SessionFactoryUtils creates a new one if allowCreate is true. So, allowCreate has no effect if a Session is already bound to the thread.
Loumeister
Sep 27th, 2004, 11:05 PM
If a Hibernate Session is already bound to the thread, and has no entityInterceptor configured, applying your interceptor to HibernateTemplate will not work for you since SessionFactoryUtils always returns the pre-bound HibernateSession.
Thanks for increasing my understanding in this area. It sounds like using two SessionFactories (one with the interceptor, one without) seems like the best solution for me. This solution does work for me fine, however are they any downsides to doing this? CMT (both appear to still fall under my transaction), DB Connections, etc?
Lou
spring04
Mar 18th, 2005, 11:58 PM
,
I've the same requirement that i would like to record some history information when the a record in the table is inserted/updated/deleted (CRUD). I'm trying to implement as discussed here....the spring way.
I've the AuditLogInterceptor class that implements Interceptor as follows:
Code:
public class AuditLogRecordInterceptor implements Interceptor {
.....
......
* *public void postFlush(Iterator arg0) throws CallbackException {
* ** *try {
* ** ** *for(Iterator itr = inserts.iterator(); itr.hasNext(); ) {
* ** ** ** *Auditable entity = (Auditable)itr.next();
* ** ** ** *AuditLog.logEvent("create", entity, "testuser");
* ** ** *}
* ** ** *for(Iterator itr = updates.iterator(); itr.hasNext(); ) {
* ** ** ** *Auditable entity = (Auditable)itr.next();
* ** ** ** *AuditLog.logEvent("update", entity, "testuser");
* ** ** *}
* ** *} catch(HibernateException e) {
* ** ** *
* ** *} finally {
* ** ** *inserts.clear();
* ** ** *updates.clear();
* ** *}
* ** *
* *}
}
In this class i'm implementing the required methods such as OnSave, OnPostFlush etc as per the hibernate requirement.
I also have a class called AuditLog an utility class as follows:
Code:
public class AuditLog {
* *private SessionFactory sessionFactory;
* *
* *public void setSessionFactory(SessionFactory sessionFactory) {
* ** *this.sessionFactory = sessionFactory;
* *}
* *
* *public SessionFactory getSessionFactory() {
* ** *return sessionFactory;
* *}
* *
* *public void logEvent(String message, Auditable entity,
* ** ** ** ** ** ** *Long userId)
* ** ** ** ** ** ** ** *throws CallbackException {
* ** *Session session = null;
* ** *
* ** *try {
* ** ** *AuditLogRecord record = new AuditLogRecord(message, entity.getId(),
* ** ** ** ** ** ** ** *entity.getClass(), userId);
* ** ** * session = sessionFactory.openSession();
* ** ** *session.save(record);
* ** ** *session.flush();
* ** *} catch(Exception ex) {
* ** ** *throw new CallbackException(ex);
* ** *} finally {
* ** ** *try {
* ** ** ** *session.close();
* ** ** *} catch(HibernateException ex) {
* ** ** ** *throw new CallbackException(ex);* ** ** ** *
* ** ** *}
* ** *}
* ** *
* *}
}
Here is my applicationContext.xml file (partial)
Code:
* ** * <!-- Hibernate SessionFactory 2 used for audit log-->
* * <bean id="sessionFactory2" class="org.springframework.orm.hibernate.LocalSessionFact oryBean">
* * * * <property name="dataSource"><ref local="dataSource"/></property>
** ** *<property name="mappingResources">
* ** ** *<list>
* ** ** ** *<value>com/apple/ermt/model/Project.hbm.xml</value>
* ** ** ** *<value>com/apple/ermt/model/ProjectResource.hbm.xml</value>
* ** ** ** *<value>com/apple/ermt/model/Resource.hbm.xml</value>
* ** ** ** *<value>com/apple/ermt/model/Requirement.hbm.xml</value>* *
* ** ** ** *<value>com/apple/ermt/model/TestCase.hbm.xml</value>* *
* ** ** ** *<value>com/apple/ermt/model/UseCase.hbm.xml</value>* ** ** ** ** ** *
* ** ** *</list>
* ** *</property>
* * </bean>
*
* *<bean id="auditLogInterceptor" class="com.apple.ermt.persistence.audit.AuditLogIntercept or" singleton="false"/>
* *
* *<bean id="auditLog" class="com.apple.ermt.persistence.audit.AuditLog">
* ** *<property name="sessionFactory"><ref local="sessionFactory2"/></property>
* *</bean>
* *
* * <!-- Hibernate SessionFactory -->
* * <bean id="sessionFactory" class="org.springframework.orm.hibernate.LocalSessionFact oryBean">
* * * * <property name="dataSource"><ref local="dataSource"/></property>
** ** *<property name="mappingResources">
* ** ** *<list>
* ** ** ** *<value>com/apple/ermt/model/Project.hbm.xml</value>
* ** ** ** *<value>com/apple/ermt/model/ProjectResource.hbm.xml</value>
* ** ** ** *<value>com/apple/ermt/model/Resource.hbm.xml</value>
* ** ** ** *<value>com/apple/ermt/model/Requirement.hbm.xml</value>* *
* ** ** ** *<value>com/apple/ermt/model/TestCase.hbm.xml</value>* *
* ** ** ** *<value>com/apple/ermt/model/UseCase.hbm.xml</value>* ** ** ** ** ** *
* ** ** *</list>
* ** *</property>
* * * * <property name="hibernateProperties">
* * * * <props>
* * * * * * <prop key="hibernate.dialect">net.sf.hibernate.dialect.MySQLDialect</prop>
* * * * * * <prop key="hibernate.hbm2ddl.auto">update</prop>
* * * * </props>
* * * * </property>*
* * </bean>
........
......
<!-- Transaction manager for a single Hibernate SessionFactory (alternative to JTA) -->
<bean id="transactionManager" class="org.springframework.orm.hibernate.HibernateTransac tionManager">
<property name="sessionFactory"><ref local="sessionFactory"/></property>
<property name="entityInterceptor"><ref local="auditLogInterceptor"/></property>
</bean>
My problem is that per the Hibernate in Action book, the AuditLog can have a static method called logEvent which can be used from the AuditLogRecord class to actually record the entry in the table. If i make this method STATIC, it can't access the instance variable sessionFactory being injected by Spring. The solution is to make this variable also STATIC. But i feel that this is WRONG. So do i need to have the logEvent method to be STATIC? If not, how else should i implement this?
Could someone please share your experience?
Thanks very much in advan
steven.warren
Mar 19th, 2005, 01:44 AM
I suggest you use database triggers. Why would you ask the wolf to keep track of how many chickens he took... :wink:
creatxr
Apr 28th, 2005, 02:07 AM
http://forum.springframework.org/viewtopic.php?t=3670&highlight=
I can log the changes by interceptor for hibernate
but , I 've still a question: how to set username using httpsession?
http://forum.springframework.org/viewtopic.php?t=560&highlight=hibernate+log+interceptor
smccrory
May 3rd, 2005, 08:52 AM
I suggest you use database triggers. Why would you ask the wolf to keep track of how many chickens he took... :wink:
Unfortunately in some cases the DB doesn't have all the information necessary to log the level of detail required by Audit. For example, the user's ID, type and status.
Scott
morning
Apr 25th, 2008, 05:01 AM
Hi ,
l use two SessionFactory's ,
once l got DataIntegrityViolationException (and l catched it) because of the entity l add to the database is not unique for some field , and re-enter the entity again by correcting the mistake , will l get double entry for the auditlog ,
Hibernate: insert into database.ENTITY (ENTITY_NAME) values (?)
Hibernate: insert into database.AUDITLOG (MESSAGE, ENTITY_ID, ENTITY_CLASS, USERNAME, CREATED) values (?, ?, ?, ?, ?)
Hibernate: insert into database.AUDITLOG (MESSAGE, ENTITY_ID, ENTITY_CLASS, USERNAME, CREATED) values (?, ?, ?, ?, ?)
what's happening ?
yatgor
Powered by vBulletin® Version 4.2.1 Copyright © 2013 vBulletin Solutions, Inc. All rights reserved.