Easy Entity AuditingWith Hibernate Envers
Romain LinsolasJava & Web Developer
Société Générale
@romaintaz
3
Romain Linsolas
Who am I?■ Java & Web developer;■ Technical architect, Software Factory gardener;■ @ Société Générale (french bank)
http://linsolas.free.fr/wordpress
@romaintaz
https://github.com/linsolas
(1) What is Hibernate Envers?
Definition
Hibernate Envers
Hibernate module to enable easy auditing of persistent classes!
Audit = keep a revision of your entity after every "event" (insert, update, delete)
Available since 2009…
http://www.jboss.org/envers
http://docs.jboss.org/envers/docs/index.html
http://docs.jboss.org/hibernate/orm/4.1/devguide/en-US/html/ch15.html
(2) Activation
7
Add the Envers library in classpath
<!-- Requires:
* Hibernate 3+
* Hibernate annotations -->
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-envers</artifactId>
<version>4.1.8.Final</version>
</dependency>
(3) Start the audit
9
Audit a simple entity class@Entity
@Audited
public class Person {
@Id @GeneratedValue
private int id;
private String name;
private String surname;
@ManyToOne
private Address address;
// getters, setters, constructors, equals and hashCode…
}
10
Some useful Envers @nnotations@Audited
@AuditTable(“T_PERSON_AUDIT”)
public class Person {
@NotAudited
private String comments;
@AuditJoinTable(name=“T_PERSON_ADDRESS_AUDIT”,
inverseJoinColumns=@JoinColumn(name=“ADDRESS_ID”))
public List<Address> address;
}
What about the database?
Table T_PERSON
Id Name Surname Comments
Table T_PERSON_AUD
Id REV Name Surname REVTYPE
Table REVINFO
REV REVTSTMP (additional info)
0 Add
1 Mod
2 Del
12
Additional information in revisions@Entity
@RevisionEntity(UsernameRevisionListener.class)
public class MyEntityRevision extends DefaultRevisionEntity {
private String username; // + getter / setter
}
public class UsernameRevisionListener implements RevisionListener {
@Override
public void newRevision(Object revisionEntity) {
((MyEntityRevision) revisionEntity).setUsername(getCurrentUsername());
}
}
Tracking modified fields<property name="org.hibernate.envers.global_with_modified_flag" value="true"/>
Or
@Audited(withModifiedFlag = true)
private String myField;
Table T_PERSON_AUD
Id REV Name Name_MOD Surname Surname_MOD REVTYPE
Still an experimental feature…
(4) Query audit information
15
AuditReader// Get all the revisions of my current object
int personId = somePerson.getId();
AuditReader auditReader = AuditReaderFactory.get(entityManager);
List<Number> allRevisions = auditReader.getRevisions(Person.class, personId);
for (Number n: allRevisions) {
Person p = auditReader.find(Person.class, personId, n);
System.out.printf("\t[Rev #%1$s] > %2$s\n", n, p);
}
[Rev #1] > Person { id=10, name='Romain', surname='', comments=''}
[Rev #3] > Person { id=10, name='Romain', surname='Linsolas', comments=''}
[Rev #4] > null
16
AuditQuery - 1
AuditQuery query1 = auditReader.createQuery()
.forEntitiesAtRevision(Person.class, 42);
List<Person> persons = query1.getResultList();
Person { id=11, name='Chuck', surname='Norris', comments=''}
Person { id=10, name='Romain', surname='Linsolas', comments=''}
17
AuditQuery - 2
AuditQuery query2 = auditReader.createQuery()
.forRevisionsOfEntity(Person.class, false, true);
List<Object[]> revisions = query2.getResultList();
Person EntityRevision REVTYPE{ id=10, name='Romain', surname='', comments='' }
{id=1, timestamp=1352936106653, username='Devoxx'} ADD
{ id=11, name='Chuck', surname='Norris', comments='' }
{id=2, timestamp=1352936106669, username='Devoxx'} ADD
{ id=10, name='Romain', surname='Linsolas', comments='' }
{id=3, timestamp=1352936106687, username='Devoxx'} MOD
{ id=10, name='null', surname='', comments='' }
{id=4, timestamp=1352936106734, username='Devoxx'} DEL
18
AuditQuery - 3
List<Person> persons = auditReader.createQuery()
.forEntitiesAtRevision(Person.class, 42)
.addOrder(AuditEntity.property(“surname”).desc())
.add(AuditEntity.relatedId(“address”).eq(theAddressId))
.setFirstResult(4)
.setMaxResults(2)
.getResultList();
AuditQuery - 4
AuditQuery query = auditReader().createQuery()
.forEntitiesAtRevision(Person.class, 42)
.add(AuditEntity.property("surname").hasChanged())
.add(AuditEntity.property("name").hasNotChanged());
Demo
https://github.com/linsolas/devoxx-envers
To summarize…
Pros
Really easy to use!
Configurable
Ready-to-use audit query tool
Fully integrated in Hibernate
No similar project (?)
Cons
Require Hibernate
Not compatible with Hibernate XML configuration (cf. HHH-3887)
Q&A
(5) Annexes
24
Register Envers Listener<!-- Needed in persistence.xml or Hibernate configuration if you use older versions of Envers (3.x) -->
<property name="hibernate.ejb.event.post-insert"
value="org.hibernate.ejb.event.EJB3PostInsertEventListener,org.hibernate.envers.event.AuditEventListener" />
<property name="hibernate.ejb.event.post-update"
value="org.hibernate.ejb.event.EJB3PostUpdateEventListener,org.hibernate.envers.event.AuditEventListener" />
<property name="hibernate.ejb.event.post-delete"
value="org.hibernate.ejb.event.EJB3PostDeleteEventListener,org.hibernate.envers.event.AuditEventListener" />
<property name="hibernate.ejb.event.pre-collection-update"
value="org.hibernate.envers.event.AuditEventListener" />
<property name="hibernate.ejb.event.pre-collection-remove"
value="org.hibernate.envers.event.AuditEventListener" />
<property name="hibernate.ejb.event.post-collection-recreate"
value="org.hibernate.envers.event.AuditEventListener" />