Have you tracked your Entity Objects ? When it has created,modified and deleted with time.
Try Envers for Easy Auditing of Entity Classes. Very simple to audit your Entity classes using @Audited. Envers now becomes a part of Hibernate 3.5.
List of libraries you need for this
- hibernate3.jar
- antlr.jar
- commons-collections.jar
- dom4j-1.6.1.jar
- javassist.jar
- jpa-api-2.0-1.jar
- jta.jar
- mysql-connector-java-5.1.3-rc-bin.jar
- slf4j-api-1.6.1.jar
1. hibernate.cfg.xml
Add the Audit Event Listeners in your hibernate.cfg.xml
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE hibernate-configuration PUBLIC "-//Hibernate/Hibernate Configuration DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd"> <hibernate-configuration> <session-factory> <!-- Database connection settings --> <property name="connection.driver_class">com.mysql.jdbc.Driver</property> <property name="connection.url">jdbc:mysql://localhost:3306/envers</property> <property name="connection.username">root</property> <property name="connection.password">welcome123</property> <!-- JDBC connection pool (use the built-in) --> <property name="connection.pool_size">1</property> <!-- SQL dialect --> <property name="dialect">org.hibernate.dialect.MySQLDialect</property> <!-- Enable Hibernate's automatic session context management --> <property name="current_session_context_class">thread</property> <!-- Disable the second-level cache --> <property name="cache.provider_class">org.hibernate.cache.NoCacheProvider</property> <!-- Echo all executed SQL to stdout --> <property name="show_sql">true</property> <!-- Drop and re-create the database schema on startup --> <property name="hbm2ddl.auto">update</property> <mapping/> <!-- Hibernate ENVERS Listener Configuration --> <listener class="org.hibernate.envers.event.AuditEventListener" type="post-insert"/> <listener class="org.hibernate.envers.event.AuditEventListener" type="post-update"/> <listener class="org.hibernate.envers.event.AuditEventListener" type="post-delete"/> <listener class="org.hibernate.envers.event.AuditEventListener" type="pre-collection-update"/> <listener class="org.hibernate.envers.event.AuditEventListener" type="pre-collection-remove"/> <listener class="org.hibernate.envers.event.AuditEventListener" type="post-collection-recreate"/> </session-factory> </hibernate-configuration>
2. Entity Class
Your Entity class should have @Audited for tracking the values in the persistent class.
package com; import java.io.Serializable; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.Id; import javax.persistence.Table; import org.hibernate.envers.Audited; /** * @author Anand * */ @Entity @Table(name="user") @Audited //---------------- This is more important!!!!!!!! public class User implements Serializable { @Id @GeneratedValue @Column(name="id") private int id; @Column(name="firstname") private String firstname; @Column(name="lastname") private String lastname; @Column(name="email") private String email; /** * @return the email */ public String getEmail() { return email; } /** * @param email the email to set */ public void setEmail(String email) { this.email = email; } /** * @return the firstname */ public String getFirstname() { return firstname; } /** * @param firstname the firstname to set */ public void setFirstname(String firstname) { this.firstname = firstname; } /** * @return the id */ public int getId() { return id; } /** * @param id the id to set */ public void setId(int id) { this.id = id; } /** * @return the lastname */ public String getLastname() { return lastname; } /** * @param lastname the lastname to set */ public void setLastname(String lastname) { this.lastname = lastname; } }
3. DB Structure of your Entity class
Field | Type | Null | Key | Default | Extra |
id | int(11) | NO | PRI | NULL | auto_increment |
firstname | varchar(128) | YES | NULL | ||
lastname | varchar(128) | YES | NULL | ||
varchar(64) | YES | NULL |
4. Insert the data
Now lets begin with Insert the data
Add the data to your Entity Class
User user = new User(); user.setFirstname("biju"); user.setLastname("cd"); user.setEmail("cdbiju@gmail.com");
Get the Session Factory and Session to save the data into DB
/** Getting the Session Factory and session */ //SessionFactory sessionfactory = HibernateUtil.getSessionFactory(); SessionFactory sessionfactory = new AnnotationConfiguration().configure().buildSessionFactory(); Session sess = sessionfactory.getCurrentSession(); /** Starting the Transaction */ Trans)action tx = sess.beginTransaction(); /** Saving POJO */ sess.save(user); /** Commiting the changes */ tx.commit(); System.out.println("Record Inserted"); /** Closing Session */ sessionfactory.close();
5. New Tables Created for holding the Revision Entries
Table 1 : user_aud(Audit Table)
Audit table will have the default suffix to be _aud and present in the default schema of the database.
It has the same structure as the Entity table. Additionally it has three columns in it namely
- id
- REV
- REVINFO
Field | Type | Null | Key | Default | Extra |
id | int(11) | NO | PRI | NULL | auto_increment |
REV | int(11) | NO | PRI | NULL | |
REVTYPE | tinyint(11) | YES | |||
firstname | varchar(128) | YES | NULL | ||
lastname | varchar(128) | YES | NULL | ||
varchar(64) | YES | NULL |
Table 2 : revinfo(common table for the Entity)
This revinfo table will be common for all the Entity classes.
Field | Type | Null | Key | Default | Extra |
REV | int(11) | NO | PRI | NULL | auto_increment |
REVTSTMP | bigint(20) | YES |
This two tables will be automatically created.
6. Entries in the Audit table and Revinfo table after insert
id | REV | REVTYPE | firstname | lastname | |
1 | 1 | 0 | cdbiju@gmail.com |
biju |
cd |
Here the id column is foreign key for the entity class “user”, REV will be primary key for the audited table. More Importantly the REVTYPE has three values in it.
0 = Creation
1 = Update
2 = Delete
Whenever the insertion takes for the entity class “user”, it makes an entry as Zero and keeps all the Audited columns values in it. (0 = Creation)
Look into Revinfo table
REV | REVTSTMP |
1 | 134343453534434 |
It contains the Revision Timestamp value.
7. Update the data and Look into Audit Tables
User user = new User(); user.setId(1); // Passes the id=1 to the Entity Class
/** Getting the Session Factory and session */ SessionFactory sessionfactory = HibernateUtil.getSessionFactory(); Session sess = sessionfactory.getCurrentSession(); /** Starting the Transaction */ Transaction tx = sess.beginTransaction(); User u = (User) sess.get(User.class, user.getId()); u.setFirstname("biju-append"); u.setLastname("cd-append"); u.setEmail("cdbiju-append@gmail.com"); sess.saveOrUpdate(u); /** Commiting the changes */ tx.commit(); System.out.println("Record Updated"); /** Closing Session */ sessionfactory.close();
Look into the Audit Table
id | REV | REVTYPE | firstname | lastname | |
1 | 1 | 0 | cdbiju@gmail.com |
biju |
Cd |
1 | 2 | 1 | cdbiju-append@gmail.com |
biju-append
|
cd-append
|
Note 1 is for Update (REVTYPE)
REV | REVTSTMP |
1 | 134343453534434 |
2 | 134343453534434 |
When we update the same record, it updates the Audited columns values and update the REVTYPE to be 1.( 1 = Updation)
8. Delete the data and Look into Audit Tables
User user = new User(); user.setId(1); // Passes the id=1 to the Entity Class to delete
/** Getting the Session Factory and session */ SessionFactory sessionfactory = HibernateUtil.getSessionFactory(); Session sess = sessionfactory.getCurrentSession(); /** Starting the Transaction */ Transaction tx = sess.beginTransaction(); User u = (User) sess.get(User.class, user.getId()); sess.delete(u); /** Commiting the changes */ tx.commit(); System.out.println("Record Deleted"); /** Closing Session */ sessionfactory.close();
Look into the Audit Table
id | REV | REVTYPE | firstname | lastname | |
1 | 1 | 0 | cdbiju@gmail.com |
biju |
Cd |
1 | 2 | 1 | cdbiju-append@gmail.com |
biju-append
|
cd-append
|
1 | 3 | 2 | NULL |
NULL |
NULL |
Note 2 is for Delete (REVTYPE)
REV | REVTSTMP |
1 | 134343453534434 |
2 | 134343453534434 |
3 | 134343452343243 |
When the data is Deleted, the row has been deleted in the Entity table . But in the Audited Table, it updates all the Audited columns to be NULL and updates the REVTYPE to be 2.(2 = Delete)
Thanks for the article Anand – sounds like a good idea. Have you any notion of the overhead? Is it possible to turn off auditing for non-necessary fields? Perhaps big text fields…
Is this something coming though JPA or purely Hibernate?
Rob
🙂
Thanks Robert. Yes it is possible to non audit some of the fields in the entity class by using the Annotation @Audited(targetAuditMode = RelationTargetAuditMode.NOT_AUDITED)..
Now Envers has been added to the hibernate-core3.5.
Interesting, but I have to say, not designed with a “production” app in mind. My argument:
1) Dynamic generation of database tables is frowned upon (if not flat out disallowed) in most production database setups. In fact, the way we have things running, our apps connect with a different user that is not schema owner and our batch applications also connect with a different user (different from the app user). So this will not fly.
2) For the audit table, is there any configuration on how many versions to retain or is that a manual task for the developer?
3) What is most important in an audit table is who made the change and when. I find it puzzling that the revinfo table is common to all entities? How does one associate the rev with a particular entity? Is there an opportunity to add additional app specific metadata?
4) How does the version attribute of a versioned entity fit with revisions?
Hibernate – Envers – Easy Auditing for Entity Classes « get2java…
Thank you for submitting this cool story – Trackback from Java Pins…