DEV Community

Oleksii Kondratiuk for Base Blocks tech

Posted on

How-to #2. Audit trail using envers

Motivation

Consider case when changes that user or some system makes to the data in the database need to be tracked. Sometimes it is the requirement from the officials to have ability to get history of this changes, sometimes we just want to have the view on what happened in the system for error processing/debug/monitoring purposes or it might be we just want to have historical data about how, for example, height of a kid changed. All this cases can be covered with pretty simple solution - logging of the latest state of the data at the moment of changes. It is called audit trail.

Requirements

  1. Changes in tables are tracked automatically whenever we call save, update or delete operations on our repository level;
  2. Ability to easily query audit trail.

Tech solution

We are going to work with our project User service which was used for previous article in the series. Our goal to have audit trail of all the changes of the user data.

Spring Data has the mechanism called Spring Data Envers which allow access to entity revisions managed by Hibernate Envers. It will automatically write entities snapshots of the User entity on the update, save and delete operation into separate table(user_history in our case). Let's have a look how it can be implemented.

Respectful dependency should be added to enable it in our project:
compile group: 'org.springframework.data', name: 'spring-data-envers'

Repository factory bean should be changed. It can be done by adding EnableJpaRepositories(or modifying if you already have it) on the Application class(class marked by @Configuration) with the EnversRevisionRepositoryFactoryBean as factory bean class:
@EnableJpaRepositories(repositoryFactoryBeanClass = EnversRevisionRepositoryFactoryBean.class)

Audited annotation should be added to enable audit trail of the particular entity:
@Entity
@Data
@Table(name = "bb_user")
@EqualsAndHashCode(onlyExplicitlyIncluded = true)
@Audited
public class User {
...

Repository should extend one more interface - RevisionRepository. It enables repo to access revisions of the entity when you will want to get all the data revisions or some particular one:
public interface UserRepository extends RevisionRepository<User,Long, Integer>, CrudRepository<User, Long> {
...

There is no need to define user_history entity. You should just specify the suffix that Spring Data will add automatically add to the name of the entity and will use as a name of the table to which audit trail will be written. It is configurable in application.yaml:
spring.jpa.properties.org.hibernate.envers.audit_table_suffix: _history

In our case user_history is the table that will be used for audit trail. Let's configure Spring JPA to create tables on the application startup(set spring.jpa.generate-ddl to true and spring.jpa.hibernate.ddl-auto to create), start the application and have a look at this table. Now we have user and usera_history tables in the database. user_history table has two additional columns in comparison to the user table - rev and revtype. Revision is store in the rev column. revtype defines the type of the operation that was performed on the entity:

  1. 0 - ADD - A database table row was inserted;
  2. 1 - MOD - A database table row was updated;
  3. 2 - DEL - A database table row was deleted.

Moving to the test. First user needs to be created. In this case new records will be created in the user database with revtype 0 and first revision. revision is one per table, not per entity, this is important. So if I insert next user then the last revision (let`s say 2) will be taken and incremented by 1(3). Then if i insert next user then its revision will be 4. And so on.
User record will be updated, but there will be new record in user_history table with next revision in case of update operation on the existing user.

Conclusion

Spring Data Envers is a very convenient mechanism for the audit trail. It supports more complicated mappings as well. For instance, Audited annotation can be put not on the class level, but on some specific field and only that field will be audited. I encourage you to check its Javadoc and check Hibernate doc to get more insights.

Top comments (0)