Hibernate Envers and Spring Boot with Multiple Data Sources

The Hibernate Envers project aims to enable easy auditing of Persistent classes.  It completely takes away the hassles of auditing an entity.

The section below outlines the high-level steps to configure Envers with Spring boot using Custom Revision Entity. It demonstrates how Envers can be configured when multiple data sources are involved.

Getting Started with Envers

If you are using Maven, add the below configuration for Envers in pom.xml


Creating Custom Revision Entity

In many scenarios, you would need a custom revision entity as default revision entity fields may not suffice. Below is an example of creating a Custom Revision entity.

The example uses a sequence for id, but there can be different strategies that can be used for id generation.

Pay attention to @RevisionNumber and @RevisionEntity which are used by Envers for creating Revision Entity and persisting the value in the database.

package com.example.service.audit.entity

@Table(name = "app_user_rev_entity", schema = "application")
public class UserRevEntity implements Serializable {

private static final long serialVersionUID = 1L;

@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "user_rev_generator")
@SequenceGenerator(name = "user_rev_generator", allocationSize = 10,sequenceName = "app_userrev_seq")
private int id;

private Date date;

@Column(name = "user_name")
private String userName;

@Column(name = "user_id")
private Long userId;

// Getters, setters, equals, hashcode ….

UserRevisionListener is the class where all the custom attributes for the UserRevEntity are populated.

The example below used Spring Boot principal user to get the username. Similarly, other attributes can also be populated. The class should implement the RevisionListener interface from Hibernate Envers.

package com.example.service.audit.entity

public class UserRevisionListener implements RevisionListener {

* @see org.hibernate.envers.RevisionListener#newRevision(java.lang.Object)
public void newRevision(Object userRevision) {

Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
User authenticatedUser = (User) authentication.getPrincipal();

UserRevEntity userRevEntity = (UserRevEntity) userRevision;

Configure application.properties

Below are some of the configurations specific to Hibernate Envers. More details about the configuration properties can be found here.



You can use spring.jpa.hibernate.ddl-auto=update if you want Hibernate to create custom revision entity and other audit tables for you, but this is not something I would recommend for a production environment. It’s best to create the tables yourself in a production environment.

The next section is about the audit strategy. Default is good but ValidityAuditStrategy is a more advanced strategy.

Configure an Entity to be Audited

Below is an example of how to audit an Entity class. All we need to do is add @Audited annotation at the class level (if all the attributes have to be audited) or add the annotation as an individual attribute level.

@Table(name = "app_user", schema = "application")
public class UserEntity implements Serializable {

private static final long serialVersionUID = 1L;

@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "user_generator")
@SequenceGenerator(name = "user_generator", allocationSize = 1, sequenceName = "application.app_userid_seq")
@Column(name = "user_id")
private Long userId;

@Column(name = "first_name")
private String firstName;

Configure Envers with Multiple Datasources

In many applications, you will have multiple data sources as you may want to store different data in different schemas or maybe even different databases. The section below outlines the steps involved in configuring Envers with multiple data sources.

Configure multiple datasources in application.properties file.

application.spring.datasource.url = jdbc:postgresql://xxxx
application.spring.datasource.username = xxx
application.spring.datasource.password=xxx application.spring.datasource.testWhileIdle = true
application.spring.datasource.validationQuery = SELECT 1

example.spring.datasource.url = jdbc:postgresql://xxx
example.spring.datasource.username = xxx
example.spring.datasource.testWhileIdle = true
example.spring.datasource.validationQuery = SELECT 1

Below are the configurations needed for Spring Boot to identify and load the data source to be used.

@EnableJpaRepositories(entityManagerFactoryRef = "exampleEntityManagerFactory", transactionManagerRef = "exampleTransactionManager", basePackages = {
"com.example.service.example.entity" })
public class ExampleDatabaseConfig {

@Bean(name = "exampleDataSource")
@ConfigurationProperties(prefix = "example.spring.datasource")
public DataSource dataSource() {
return DataSourceBuilder.create().build();

@Bean(name = "exampleEntityManagerFactory")
public LocalContainerEntityManagerFactoryBean exampleEntityManagerFactory(EntityManagerFactoryBuilder builder,
@Qualifier("exampleDataSource") DataSource dataSource) {
return builder.dataSource(dataSource).packages("com.example.service.audit.entity").persistenceUnit("example").build();

@Bean(name = "exampleTransactionManager")
public PlatformTransactionManager exampleTransactionManager(
@Qualifier("exampleEntityManagerFactory") EntityManagerFactory exampleEntityManagerFactory) {
return new JpaTransactionManager(exampleEntityManagerFactory);


@EnableJpaRepositories(entityManagerFactoryRef = "applicationEntityManagerFactory", transactionManagerRef = "applicationTransactionManager", basePackages = {
"com.application.service.example.entity "
public class ApplicationDatabaseConfig {

@Bean(name = "applicationDataSource")
@ConfigurationProperties(prefix = "application.spring.datasource")
public DataSource dataSource() {
return DataSourceBuilder.create().build();

@Bean(name = "applicationEntityManagerFactory")
public LocalContainerEntityManagerFactoryBean entityManagerFactory(EntityManagerFactoryBuilder builder,
@Qualifier("applicationDataSource") DataSource dataSource) {
return builder.dataSource(dataSource)

@Bean(name = "applicationTransactionManager")
public PlatformTransactionManager transactionManager(
@Qualifier("applicationEntityManagerFactory") EntityManagerFactory entityManagerFactory) {
return new JpaTransactionManager(entityManagerFactory);

In both the above configurations, the important piece to remember is to add the package where Envers Custom Revision Entity class is implemented — in this case add “com.example.service.audit.entity” to both the Datasource configuration files for Entity scan by Envers.

If you miss adding the package in one of the configuration files, you might suddenly start seeing the below errors when trying to persist the entity which is being audited. The code might not have changed, but you may start seeing failures.

This depends on the ClassLoader on which the data source is loaded first at the boot time. If the data source which has the entity scan package missing is loaded last, you will see the below error.

This may lead to a lot of time being wasted in debugging the issue and you may be misled into creating the missing hibernate_sequence, a generic sequence used by Hibernate Envers.

Caused by: org.hibernate.exception.SQLGrammarException: could not extract ResultSet
at org.hibernate.exception.internal.SQLStateConversionDelegate.convert(SQLStateConversionDelegate.java:106)
at org.hibernate.exception.internal.StandardSQLExceptionConverter.convert(StandardSQLExceptionConverter.java:42)
at org.hibernate.engine.jdbc.spi.SqlExceptionHelper.convert(SqlExceptionHelper.java:109)
at org.hibernate.engine.jdbc.spi.SqlExceptionHelper.convert(SqlExceptionHelper.java:95)
at org.hibernate.engine.jdbc.internal.ResultSetReturnImpl.extract(ResultSetReturnImpl.java:79)
at org.hibernate.id.enhanced.SequenceStructure$1.getNextValue(SequenceStructure.java:96)
at org.hibernate.id.enhanced.NoopOptimizer.generate(NoopOptimizer.java:40)
at org.hibernate.id.enhanced.SequenceStyleGenerator.generate(SequenceStyleGenerator.java:412)
at org.hibernate.event.internal.AbstractSaveEventListener.saveWithGeneratedId(AbstractSaveEventListener.java:101)
at org.hibernate.jpa.event.internal.core.JpaSaveEventListener.saveWithGeneratedId(JpaSaveEventListener.java:56)
at org.hibernate.event.internal.DefaultSaveOrUpdateEventListener.saveWithGeneratedOrRequestedId(DefaultSaveOrUpdateEventListener.java:192)
at org.hibernate.event.internal.DefaultSaveEventListener.saveWithGeneratedOrRequestedId(DefaultSaveEventListener.java:38)
at org.hibernate.event.internal.DefaultSaveOrUpdateEventListener.entityIsTransient(DefaultSaveOrUpdateEventListener.java:177)
at org.hibernate.event.internal.DefaultSaveEventListener.performSaveOrUpdate(DefaultSaveEventListener.java:32)
at org.hibernate.event.internal.DefaultSaveOrUpdateEventListener.onSaveOrUpdate(DefaultSaveOrUpdateEventListener.java:73)
at org.hibernate.internal.SessionImpl.fireSave(SessionImpl.java:679)
at org.hibernate.internal.SessionImpl.save(SessionImpl.java:671)
at org.hibernate.envers.internal.revisioninfo.DefaultRevisionInfoGenerator.saveRevisionData(DefaultRevisionInfoGenerator.java:75)
at org.hibernate.envers.internal.synchronization.AuditProcess.getCurrentRevisionData(AuditProcess.java:119)
at org.hibernate.envers.internal.synchronization.AuditProcess.executeInSession(AuditProcess.java:96)

... 141 common frames omitted
Caused by: org.postgresql.util.PSQLException: ERROR: relation "hibernate_sequence" does not exist
Position: 17


Hibernate Envers is a mature auditing module provided by Hibernate. It is highly configurable and saves the effort of building an auditing framework.

However, when using multiple data sources, remember to configure the entity scan package in both the data sources, or it would mislead you and take away a lot of your time in debugging the issue, especially when the working code suddenly starts failing.

Read our post “Front-end Browser Debugging” for tools and tricks to troubleshoot issues with your browser.

Share on facebook
Share on linkedin
Share on twitter

About Intentwise:

Intentwise is an Amazon Advertising / Retail Media optimization platform. Intentwise’s industry-leading technology platform provides impactful recommendations, automation, and analytics to amplify advertising performance for brands, sellers, and agencies.

2 Responses

  1. Thanks and congratulations for this article!

    I’ve tried using multiple data sources intended to separate the audited tables from the audit data, as it seems that Envers has no support to that. It didn’t work! But it’s not a problem of your instructions… Nevertheless it would be an interesting thing to achieve.

    Furthermore I’d like to say that creating the transaction managers by yourself seems to force you to insert the database properties (e.g. charset, dialect…) within the code. In other words the properties set in application.properties are not used anymore. For some settings it might get you into some troubles (at least for already working applications) because the properties read in LocalContainerEntityManagerFactoryBean are different from those in application.properties. Is there a corresponding property to each property written in application.properties?

Leave a Reply

Your email address will not be published. Required fields are marked *