Easy Entity Auditing
With Hibernate Envers




    Romain Linsolas
    Java & Web Developer
      Société Générale
      @romaintaz
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
                                                     3
(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
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>




                                               7
(3) Start the audit
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…
 }
                                                               9
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;


 }




                                                             10
What about the database?

     Table T_PERSON
     Id        Name    Surname           Comments



      Table T_PERSON_AUD                                              0     Add
      Id       REV         Name         Surname     REVTYPE           1     Mod
                                                                      2     Del




                                  Table REVINFO
                                  REV      REVTSTMP     (additional info)
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());
     }
 }
                                                                                  12
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
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$sn", n, p);
 }


     [Rev #1] > Person { id=10, name='Romain', surname='', comments=''}
     [Rev #3] > Person { id=10, name='Romain', surname='Linsolas', comments=''}
     [Rev #4] > null


                                                                                  15
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=''}




                                                                      16
AuditQuery - 2
 AuditQuery query2 = auditReader.createQuery()
          .forRevisionsOfEntity(Person.class, false, true);
 List<Object[]> revisions = query2.getResultList();




 Person                                     EntityRevision                                       REVTYPE
 { id=10, name='Romain', surname='',        {id=1, timestamp=1352936106653, username='Devoxx'}   ADD
 comments='' }
 { id=11, name='Chuck', surname='Norris',   {id=2, timestamp=1352936106669, username='Devoxx'}   ADD
 comments='' }
 { id=10, name='Romain',                    {id=3, timestamp=1352936106687, username='Devoxx'}   MOD
 surname='Linsolas', comments='' }
 { id=10, name='null', surname='',          {id=4, timestamp=1352936106734, username='Devoxx'}   DEL
 comments='' }




                                                                                                           17
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();




                                                                     18
AuditQuery - 4

 AuditQuery query = auditReader().createQuery()
      .forEntitiesAtRevision(Person.class, 42)
      .add(AuditEntity.property("surname").hasChanged())
      .add(AuditEntity.property("name").hasNotChanged());
https://github.com/linsolas/devoxx-envers
To summarize…
  Pros                              Cons




 Really easy to use!              Require Hibernate
 Configurable                     Not compatible with Hibernate XML
 Ready-to-use audit query tool     configuration (cf. HHH-3887)
 Fully integrated in Hibernate
 No similar project (?)
Q&A
(5) Annexes
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" />




                                                                                                                    24

Devoxx 2012 hibernate envers

  • 2.
    Easy Entity Auditing WithHibernate Envers Romain Linsolas Java & Web Developer Société Générale @romaintaz
  • 3.
    Romain Linsolas Whoam 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 3
  • 4.
    (1) What isHibernate Envers?
  • 5.
    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
  • 6.
  • 7.
    Add the Enverslibrary in classpath <!-- Requires: * Hibernate 3+ * Hibernate annotations --> <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-envers</artifactId> <version>4.1.8.Final</version> </dependency> 7
  • 8.
  • 9.
    Audit a simpleentity 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… } 9
  • 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; } 10
  • 11.
    What about thedatabase? Table T_PERSON Id Name Surname Comments Table T_PERSON_AUD 0 Add Id REV Name Surname REVTYPE 1 Mod 2 Del Table REVINFO REV REVTSTMP (additional info)
  • 12.
    Additional information inrevisions @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()); } } 12
  • 13.
    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…
  • 14.
    (4) Query auditinformation
  • 15.
    AuditReader // Getall 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$sn", n, p); } [Rev #1] > Person { id=10, name='Romain', surname='', comments=''} [Rev #3] > Person { id=10, name='Romain', surname='Linsolas', comments=''} [Rev #4] > null 15
  • 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=''} 16
  • 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='', {id=1, timestamp=1352936106653, username='Devoxx'} ADD comments='' } { id=11, name='Chuck', surname='Norris', {id=2, timestamp=1352936106669, username='Devoxx'} ADD comments='' } { id=10, name='Romain', {id=3, timestamp=1352936106687, username='Devoxx'} MOD surname='Linsolas', comments='' } { id=10, name='null', surname='', {id=4, timestamp=1352936106734, username='Devoxx'} DEL comments='' } 17
  • 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(); 18
  • 19.
    AuditQuery - 4 AuditQuery query = auditReader().createQuery() .forEntitiesAtRevision(Person.class, 42) .add(AuditEntity.property("surname").hasChanged()) .add(AuditEntity.property("name").hasNotChanged());
  • 20.
  • 21.
    To summarize… Pros Cons  Really easy to use!  Require Hibernate  Configurable  Not compatible with Hibernate XML  Ready-to-use audit query tool configuration (cf. HHH-3887)  Fully integrated in Hibernate  No similar project (?)
  • 22.
  • 23.
  • 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" /> 24