Envers

Hibernate, since version 4, fully supports entity versioning, and it's called envers. This is very useful for keeping track of changes to each audited entity. In this section, you will see how to configure envers, choose the right strategy for entity auditing, and handle associated entities.

Configuration

The first thing you need to do is to add the appropriate JAR and class path to your project. Hibernate envers are packaged separately, so you'll have to add the Maven dependency:

<dependency>
  <groupId>org.hibernate</groupId>
  <artifactId>hibernate-envers</artifactId>
  <version>${envers-version}</version>
</dependency>

Once you have resolved the jar dependency, you could simply add the @Audited annotation to the entity that you wish to track:

@Entity
@Audited
public class Person {
  @Id
  @GeneratedValue
  private long id;
  private String firstname;
  private String lastname;
  private String ssn;
  private Date birthdate;

  // getters and setters
}

That's it!

Obviously, Hibernate will let you further configure how the entity revisions are stored. We will discuss a few important configuration parameters. For a complete list, you can refer to the online documentation.

When you enable auditing on an entity, Hibernate will, by default, create a table using the entity name followed by the suffix _AUD. So, if you have an entity called Person that's being stored in the PERSON table, Hibernate will store the revisions of a person entity in a table called PERSON_AUD. (Note that, for Hibernate to create the tables, your hbm2ddl.auto configuration has to be set to create or update. If this is not possible, you can generate the schema using an Ant task. Refer to the Hibernate documentation for more information.)

By default, the audit tables contain the columns of the audited entity plus two additional columns: REV and REVTYPE. The REV column is the revision number that is incremented for every change to an entity. The REVTYPE column indicates what kind of a change occurred: the value 0 indicates entity creation, that is, insert, while 1 indicates update, and 2 indicates delete.

You can store the audit tables in a separate schema, so you don't clutter your application schema. For example, if you wish to store the audit tables in a schema called audited, add the following property setting to your Hibernate configuration file:

<property name="org.hibernate.envers.default_schema">
    audited
</property>

Obviously, the schema has to exist and the database user needs to have the appropriate permissions to write to it.

If your audited entity is associated with another entity or a collection of entities, the owned entity also has to be declared as Audited. In this case, you can configure Hibernate to create a new revision for the owning entity when owned entities are added or deleted. This is actually the default behavior. You can disable this by setting the following property in your configuration file:

<property name="org.hibernate.envers.revision_on_collection_change">
    false
</property>

Finally, it's even possible to track the changes to an entity property instead of the entire entity. This is achieved by simply annotating the property to be audited instead of the entity. In that case, Hibernate keeps track of the audited properties only.

Strategy

Obviously, enabling audit mechanism on your entities will have an impact on the performance of your application. When using envers, you can slightly control the impact.

The default behavior is to store a revision for each entity in the audit table along with a revision number. But, to determine when that revision was valid, another table called REVINFO is consulted; this table maintains the revision metadata. To fetch entity revisions, Hibernate performs joins and sub-selects between the entity audit table and the REVINFO table, and this impacts reading of the audit data when history is fetched.

Another strategy is to store the end revision in the audit table along with the entity data. This slows down the insertion of the audit data, slightly, but browsing through the audit history performs a little better.

To override the default behavior, set the following property in your configuration file:

<property name="org.hibernate.envers.audit_strategy">
  org.hibernate.envers.strategy.ValidityAuditStrategy
</property>

Fetching revisions

Hibernate envers offers a rich API for fetching multiple revisions of the audited entities. This is mainly done through the AuditReader interface. This API allows you to create queries, just like criteria objects. You can use it to fetch all revisions of entities of the same class or a specific entity, given that you provide the entity ID (refer to the AuditQueryCreator documentation).

For example, if you wish to fetch all the revisions of the Person entity, you can create an audit query as shown in the code here:

AuditReader reader = AuditReaderFactory.get(session);

AuditQuery query = reader
    .createQuery()
    .forRevisionsOfEntity(Person.class, true, true)
    .add(AuditEntity.id().eq(personId));

List<Person> persons = query.getResultList();

for(Person person: persons) {
  System.out.println(person);
}

Note that, in this case, we are filtering the entity by its primary ID. If you don't supply the ID, it will return all entity revisions of the Person class.

Another good example is using the AuitReader class to fetch the revision metadata, such as the revision number and timestamp. The following code demonstrates this:

AuditReader reader = AuditReaderFactory.get(session);
List<Number> revisions = reader.getRevisions(Person.class, personId);
for (Number revNum: revisions) {
  Person person = reader.find(Person.class, personId, revNum);
  System.out.println("**** revision " 
      + revNum.intValue() + " at time: " 
      + reader.getRevisionDate(revNum));
  System.out.println("------- " + person);
}

In this case, you first have to fetch the revision numbers and then look up the actual entity, using the revision number. If your entity has an update timestamp property (see the @UpdateTimestamp annotation), you can simply use the first method and order by the updated timestamp. You will see next how to order results by the entity attribute.

You can also limit the result list by setting the first result and max result, and you can also add attribute projections.

Lastly, Hibernate, by default, returns the revisions in ascending order, as it appends the SQL instruction to return the rows in the ascending order. You can, instead, order the results by a specific property of the entity. This is done by adding an order called AuditOrder to your query. You can get an instance of AuditOrder by calling AuditEntity.property("property_name").asc() or AuditEntity.property("property_name").desc():

AuditQuery query = reader
    .createQuery()
    .forRevisionsOfEntity(Person.class, true, true)
    .add(AuditEntity.id().eq(personId))
    .addOrder(AuditEntity.property("ssn").desc());
List<Person> persons = query.getResultList();

The AuditProperty API provides many additional useful methods. It is highly recommended that you look at its JavaDoc.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.137.172.115