If you are using JPA 2.0 with Hibernate and you want to do audit logging from middle-ware itself, I believe you
landed up on the exact place where you should be. You can try audit logging in
your local environment by following this post.
Required JPA/Hibernate
Maven Dependencies
<dependency>
<groupId>org.hibernate.java-persistence</groupId>
<artifactId>jpa-api</artifactId>
<version>2.0-cr-1</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-entitymanager</artifactId>
<version>3.6.4.Final</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-orm</artifactId>
<version>3.0.5.RELEASE</version>
</dependency>
|
JPA Configuration in
Spring Application Context File
For JPA/Hibernate, you need to configure entity manager,
transaction, data source and JPA vendor in your ‘applicationContext.xml’ file.
<bean id="dataSource"
class="org.springframework.jdbc.datasource.DriverManagerDataSource"
p:driverClassName="oracle.jdbc.driver.OracleDriver"
p:url="jdbc:oracle:thin:@127.0.0.1:1521/mydbservice
"
p:username="nvuser"
p:password="nvpass" />
<bean id="entityManagerFactory"
class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean"
p:persistenceUnitName="MyPersistentUnit"
p:persistenceXmlLocation="classpath*:persistence.xml"
p:dataSource-ref="dataSource"
p:jpaVendorAdapter-ref="jpaAdapter">
<property name="loadTimeWeaver">
<bean class="org.springframework.instrument.classloading.InstrumentationLoadTimeWeaver"/>
</property>
</bean>
<bean id="transactionManager"
class="org.springframework.orm.jpa.JpaTransactionManager"
p:entityManagerFactory-ref="entityManagerFactory"
/>
<bean id="jpaAdapter"
class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter"
p:database="ORACLE" p:showSql="true"
/>
|
Persistence.xml
Configuration
To enable the hibernate audit logging you need to configure
a property called ‘hibernate.ejb.interceptor’
in persistence.xml file and the value of this property should be the class that
extends the Hibernate ‘EmptyInterceptor’.
<?xml version=”1.0” encoding=”UTF-8”?>
<persistence version="2.0"
xmlns="http://java.sun.com/xml/ns/persistence"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/persistence
http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd">
<persistence-unit name=" MyPersistentUnit ">
//JPA
Entity classes are configured here
<class>com.mycom.entities.User </class>
<shared-cache-mode>ENABLE_SELECTIVE</shared-cache-mode>
<properties>
<property name="hibernate.ejb.interceptor"
value=" com.xxx.entities.baseentity.AuditLogInterceptor" />
</properties>
</persistence-unit>
</persistence>
|
Audit Logging
Interceptor Implementation
Hibernate provides an interceptor called ‘EmptyInterceptor’ with following
methods.
- onSave() – This method is called when an entity is saved, but the entity is not saved into database yet.
- onFlushDirty() – This method is called when an entity is updated, the entity is not update into database yet.
- onDelete () – This method is called when an entity is deleted , the entity is not deleted into database yet.
- preFlush() – This method is called before the saved, updated or deleted entities are not committed to the database.
- postFlush() – This method is called after the saved, updated or deleted entities are committed to database. This is called after preFlush() method.
To perform audit logging follow below steps:
- Create data base table to capture audit data:
Audit [ Row_ID, Old_Value,
New_Value, Column_Changed, Table_Changed,
Transaction_Id, User_ID]
- Create JPA entity say ‘AuditTrail’ for above table
- Create class ‘AuditLogInterceptor’ by extending Hibernate ‘EmptyInterceptor’.
- Override interceptor methods in ‘AuditLogInterceptor’
- Capture required information into AuditTrail entity and save entity
public class AuditLogInterceptor extends EmptyInterceptor {
private List<AuditTrail> auditTrailList = new ArrayList<AuditTrail>();
. . . . . .
// First Parameter: The entity which is being updated
// Second Parameter: The primary key of updated row
// Third Parameter: Current states of updated entity
// Fourth Parameter: Previous states of updated entity
// Fifth Parameter: The variable names of updated entity
// Sixth Parameter: The type of variables of updated entity
@Override public boolean onFlushDirty(Object entity, Serializable id, Object[] currentState, Object[] previousState, String[] propertyNames, Type[] types) {
// Get the
table name from entity
Class<?> entityClass =
entity.getClass();
Table tableAnnotation =
entityClass.getAnnotation(Table.class);
for (int i = 0; i < currentState.length; i++) {
// Track changes only for the states which are String or Number types
if(!(previousState[i] instanceof String
||
previousState[i] instanceof Long
||
previousState[i] instanceof BigDecimal
|| previousState[i] instanceof Integer
||
previousState[i] instanceof Timestamp))
{
continue;
}
// Check whether column value is updated
if(previousState[i] != null && currentState[i] != null
&&
!(previousState[i].equals(currentState[i]))){
AuditTrail
auditTrail = new AuditTrail();
// Set updated table name
auditTrail.setTableName(tableAnnotation.name());
//
Set updated column name
Column col = null;
try {
String
filedName = propertyNames[i];
if(filedName.contains("serialVersionUID")){
continue;
}
Character firstChar =
filedName.charAt(0);
firstChar = Character.toUpperCase(firstChar);
String filedNameSubStr =
filedName.substring(1);
String methodName = "get"+ firstChar+filedNameSubStr;
Class[]
parameterTypes = new Class[]{};
Method ueMethod = entityClass.getDeclaredMethod(methodName,
parameterTypes);
col = ueMethod.getAnnotation(Column.class);
} catch (Exception e) {
e.printStackTrace();
}
if(col != null){
auditTrail.setColumnIdentifier(col.name());
}
// Set old value
if(previousState[i] != null){
auditTrail.setOldValue(previousState[i]
== null ? null :
previousState[i].toString());
}
// Set new value
if(currentState[i] != null){
auditTrail.setNewValue(currentState[i]
== null ? null:
currentState[i].toString());
}
//
Set database operation
auditTrail.setOperation("Update");
// Set row primary key value
auditTrail.setRowIdentifier(id.toString());
auditTrailList.add(auditTrail);
}
}
return false; } @Override public void postFlush(Iterator iterator) throws CallbackException { // Set unique transaction id in all AuditTrail Entities String transId = UUID.randomUUID().toString();
for (AuditTrail auditTrail: auditTrailList) {
auditTrail.setTransactionId(transId);
} // Save ‘AuditTrail’ entity list into database using AuditDAO } } |
Audit logging Sequence Flow
Issue Faced during
Audit Logging Implementation
I
faced an interesting issue during audit logging implementation in my application. Let me share that issue detail.
I had ‘AbstractDAO’
and 'AuditDAO' class extends this ‘AbstractDAO’. 'AuditDAO' class is configured as ‘@Repository’. ‘AbstractDAO’
has entity manager property which is initialized by Spring Container. I
wanted to auto wire ‘AuditDAO’ in ‘AuditLogInterceptor’ to save ‘AuditTrail’ entity. But, this couldn’t
happen. The reason was ‘AuditLogInterceptor’
was not defined as spring resource (like @Component
or @Resources or @Repository). Hence's its property can’t be auto wired. Since, this
interceptor is initialized by hibernate, I couldn't configure this as spring
resource. Finally, I had to take the help of StackOverflow and got the idea to solve
this problem. You can refer this issue and solution from here.
Nice to have this post. I read your post and it contains pretty enough information that can describe about audit logging with hibernate.
ReplyDeleteThanks so much.
Delete