Getting Started with Hibernate Envers

Surya Sukumar
Sixt Research & Development India
6 min readNov 25, 2022

In any application, auditing means that we track and log every change in all business objects, i.e, track each insert, update and delete operations.

Basically, it involves tracking three things -

  1. What operation was performed?
  2. Who did it?
  3. When was it done?

Auditing helps us in maintaining history records, which can later help us in tracking user activities.

Hibernate Envers is a project that seeks to make auditing persistent classes simple. You only need to annotate your persistent class or a few of its attributes with “@Audited” if you wish to audit them. A table will be generated that contains the history of the changes made to each audited entity. Then, retrieving and analysing past data won’t take much work.

Process To Get Started off with Envers

  1. The first and foremost step is to have the dependency in place for the application
implementation group: 'org.springframework.data', name: 'spring-data-envers', version: '2.6.2'

2. Following this we will need a set of well-defined persistent entities in the application whose data is crucial and all operations on these entities will need to be verified at any point in time. For example -

@Entity
@Table(name = "user")
public class User {

//list of attributes

//getters

//setters

}

3. In order to enable auditing for this table we need to add the annotation @Audited on the table that is required to be tracked. This annotation will generate the audit table in the database that will record all the changes done to this entity. You can either have this annotation at entity level, which means that changes to all the attributes of that entity will be tracked, or you can have it at the attribute level which means that only that attribute changes will be tracked. For attributes that you do not require to be tracked, we can have the @NotAudited annotation on the attribute.

4. The @Audited has a flag ( withModifiedFlag ) which could be toggled to note the change in values to the attribute in the audit table (all attributes if at entity level, or only specified attribute if at attribute level). This flag is of boolean type which is true for an attribute when the value for that attribute of that entity has changed else false.

@Entity
@Audited //Entity level Auditing
@Table(name = "user")
public class User {

//list of attributes

//getters

//setters

}

// (OR)

@Entity
@Table(name = "user")
public class User {

@Audited //Attribute level auditing
private String name;

//list of attributes

//getters

//setters

}

5. To capture who created the record and when the record was created we need a set of attributes, for which JPA provides a way to capture these values using custom auditing annotations. In order for the auditing annotations to work we need to enable the entity to listen to events that change the records in the table. The attributes required are as follows —

  1. createdBy
  2. createdDate
  3. modifiedBy
  4. modifiedDate

6. These annotations ( @CreatedBy,@LastModifiedBy,@CreatedDate,@LastModifiedDate ) help to capture the auditor from security context of Spring and the time of change.

@Entity
@Audited
@EntityListeners(AuditingEntityListener.class) // enables the entity to listen to any audits
@Table(name = "user")
public class User {

//list of attributes

@CreatedBy
private String createdBy;

@LastModifiedBy
private String lastModifiedBy;

@CreatedDate
private Date createdDate;

@LastModifiedDate
private Date lastModifiedDate;

//getters

//setters

}

7. We can write custom AuditAwareListeners in order to capture the auditor name using custom logic.

public class CustomAuditAwareListener implements AuditorAware<String> {

@Override
public Optional<String> getCurrentAuditor() {

//logic to retrieve username using custom logic
}
}

Configuration

Since Envers generates some entities and maps them to tables, it is possible to set the names of the fields that are generated as well as the prefix and suffix that are added to the entity name to construct an audit table for an entity.

The list of properties that could be edited are as follows -

Config Values

The required properties can be set in the application.yml file and the audit entities generated will be user driven and more meaningful as per the industry standards.

We will need to even enable the application to support envers repository and JPA Auditing fo which we will need to configure as follows:

@SpringBootApplication
@EnableJpaAuditing(auditorAwareRef="auditorAware") //auditorAwareRef points to the name of the bean created for custom auditing listener
@EnableEnversRepositories(repositoryFactoryBeanClass = EnversRevisionRepositoryFactoryBean.class)
public class Application {

public static void main(String[] args) {
SpringApplication.run(FleetAdminApplication.class, args);
}

@Bean //can be defined in a config
public AuditorAware<String> auditorAware() {
return new CustomAuditAwareListener();
}
}

Querying Data

Now that we have the entire history of all types of operations that have taken place on the audited entities, we will need to find a way to query the data in such a way that provides meaningful information and quickly deduct what changes have been done, what data have been modified and to what was the data modified to.

We will make use of the SQL lag() function for this purpose which helps provide the above-mentioned data to us, making it useful for any business fraction of the company to read and identify the changes. An example SQL query is as follows:

SELECT id, value, lag(value) over (order by id) as previous_value FROM table_name where id=:id;

We can provide an API that lists the changes that have been made by creating a native query in a similar fashion. We need to create a POJO that contains all the information that we need and create a mapping with the results of the native query and return a meaningful response as part of a UI call which would make more sense for a User to understand the auditing done by far on the entity.

@NamedNativeQuery(
name = "user_history_query",
query ="SELECT id, tech, lag(tech) over (order by rev) as previous_tech, name, lag(name) over (order by rev) as previous_name, rev, revtype FROM user_aud where id=:id",
resultSetMapping = "user_history_dto"
)
@SqlResultSetMapping(
name = "user_history_dto",
classes = @ConstructorResult(
targetClass = AuditEntity.class,
columns = {
@ColumnResult(name = "id", type = Long.class),
@ColumnResult(name = "tech", type = String.class),
@ColumnResult(name = "previous_tech", type = String.class),
@ColumnResult(name = "name", type = String.class),
@ColumnResult(name = "previous_name", type = String.class),
@ColumnResult(name = "rev", type = Integer.class),
@ColumnResult(name = "revtype", type = Integer.class)
}
)
)
@Entity
@Audited
@Table(name = "user")
public class User {

//list of attributes

//getters

//setters

}

//(Repository Crud function)
public class Repositoy {
@Query(name = "user_history_query", nativeQuery = true)
List<AuditEntity> findUserHistory(@Param("id") Long id);
}

//(POJO you wish to map the results to)

public class AuditEntity {

private Long id;
private String tech;
private String previousTech;
private String name;
private String previousName;
private int rev;
private int revType;

//constructors

//getters

//setters

}

Screenshots

User Table
User Audit Table
Rev-Info Table
Query to fetch Audit History

--

--