In this blog, we will demonstrate how to add basic auditing your your Spring Boot application.
Table of Contents
- 1.0 Introduction
- 2.0 JPA Auditing
- 3.0 Hibernate Envers Auditing
- 3.1 How does Hibernate Envers stores audits in database
- 3.2 Add auditing tables into the database
- 3.3 Enable Auditing
- 3.3.1 Add dependency to build.gradle
- 3.3.2 Add @Audited annotation
- 3.3.3 Add configurations to application.yaml
- 3.3.4 Configure a RevisionEntity
- 3.4 Populating Logged In User Info In Revision table
- 3.5 Testing Hibernate Envers Auditing
- 3.6 Fetching Auditing Information from the application via REST API
The entire code of for this project can be found at – https://github.com/chatterjeesunit/spring-boot-app/tree/v4.0
To checkout this Release tag, run following command
git clone https://github.com/chatterjeesunit/spring-boot-app.git
cd spring-boot-app
git checkout tags/v4.0 -b v4.0-with-auditing
1.0 Introduction
In our previous blogs, we demonstrated how to create Spring Boot application and add Flyway migration, and implement Authentication/Authorization.
In this blog, we will look at how to add Auditing feature to the application.
So the question is “What is Auditing and why is it important?”
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
- What operation was performed?
- Who did it?
- When was it done?
Auditing helps us in maintaining history records, which can later help us in tracking user activities.
You can follow the steps in this blog, using any Spring Boot application. However for this blog, we will start with the base application that we build in our previous blog, which has flyway integration and authentication build into it.
You can find the starting base code for this blog in – https://github.com/chatterjeesunit/spring-boot-app/tree/v3.0
We already have a Customer
Entity in our application, and we will add auditing to this entity.
2.0 JPA Auditing
This is the simplest form of auditing.
Here we do not track what are the changes done, but only keep track of who created or modified a business entity and when it was done.
To be specific, we will keep track of following additional fields for each business object
- Created On
- Created By
- Modified On
- Modified By
2.1 Adding new audit columns to database
Let’s start by adding the Created/Modified columns into the database for Customer
table.
ALTER TABLE `customer`
ADD COLUMN `created_by` VARCHAR(50) NOT NULL,
ADD COLUMN `updated_by` VARCHAR(50) NULL,
ADD COLUMN `created_on` DATETIME NOT NULL,
ADD COLUMN `updated_on` DATETIME NULL;
You can either run the below script directly on your database, or you can add it to your Flyway/Liquibase scripts (in case you are using them or any other such database versioning tools).
2.2 Adding audit fields to entity class
Now that we have added the fields to the database, let’s add the fields to the our Customer
Entity.
Add following code.
@Column(name = "created_by")
@CreatedBy
private String createdBy;
@Column(name = "updated_by")
@LastModifiedBy
private String updatedBy;
@Column(name = "created_on")
@CreatedDate
private Date createdOn;
@Column(name = "updated_on")
@LastModifiedDate
private Date updatedOn;
- Annotations : We have added annotations from
org.springframework.data.annotation
to all the audit fields@CreatedBy
/@LastModifiedBy
: Declares a field as the one representing the principal that recently created / modified the entity containing the field
@CreatedDate
/@LastModifiedDate
: Declares a field as the one representing the date the entity containing the field was created / recently modified.
- Date Fields
- In our example we have used
java.util.Date
- The field can be any of the following types –
Joda-Time
,DateTime
, legacy JavaDate
andCalendar
, JDK8 date and time types, andlong
orLong
.
- In our example we have used
If you don’t want to add these fields and annotations on each of your entity class, your entity can also extend your entity from
org.springframework.data.jpa.domain.AbstractAuditable
2.3 Populating the logged in user info
The created and modified dates will be automatically populated by JPA.
However we have to provide details on what to persist in the created_by
and updated_by
columns.
2.3.1 Implementing AuditAware<T>
The first thing that we have to do is implement AuditAware<T>
, where T
is the type of CreatedBy/ModifiedBy field (which for us is String
)
- Create a new class
AuditAwareImpl
that implementsAuditAware<String>
- Implement the method –
public Optional getCurrentAuditor()
- Add logic to the method
getCurrentAuditor()
, to return the username of the current logged in user- We can fetch the current
SecurityContext
, fetch the currentPrincipal
from it, and then get user details from it.
- We can fetch the current
public class AuditAwareImpl implements AuditorAware<String> {
@Override
public Optional<String> getCurrentAuditor() {
return Optional.ofNullable(SecurityContextHolder.getContext())
.map(SecurityContext::getAuthentication)
.filter(Authentication::isAuthenticated)
.map(Authentication::getPrincipal)
.map(User.class::cast)
.map(User::getUsername);
}
}
2.3.2 Enable JPA Auditing
Next we will create a Configuration
to enable JPA Auditing and also provide an implementation for the AuditAware<String>
bean.
- Create a new Config class
- Add annotation
@EnableJpaAuditing
to this class - Create a method to return Bean for
AuditorAware<String>
– and return a new instance of theAuditAwareImpl
class that we had created in previous step.
@Configuration
@EnableJpaAuditing(auditorAwareRef = "customAuditProvider")
public class JpaConfig {
@Bean
public AuditorAware<String> customAuditProvider() {
return new AuditAwareImpl();
}
}
Last configuration is to associate an EntityListener
class to your audited entity. Spring already provides one such listener for us – AuditingEntityListener
@Entity
@EntityListeners(AuditingEntityListener.class) // add this line
public class Customer {
....
....
}
2.4 Testing JPA auditing.
Now that we have added all the required implementation, its time to test it.
We will test using Rest client curl
.
The examples used . or testing you have authentication/authorization already configured in the spring boot app, and it has two users in the database
– admin01@tw.com (password: admin01@123#)
– admin02@tw.com (password: admin02@123#)However these users and not mandatory.
You can test with your own set of users, by changing the curl statement in below examples.
Login as and user (admin01@tw.com) and create a customer using below command
curl -i -u admin01@tw.com:admin01@123# -X POST -H "Content-Type: application/json" http://localhost:8080/customer/ -d '{"firstName":"John","lastName":"Doe","emailAddress":"johndoe@gmail.com","addresses":[{"streetAddress":"4487 Elsie Drive","city":"Onida","stateCode":"SD","zipCode":"57564","country":"USA"}]}'
Now login as another user (admin02@tw.com) update the same customer, and change last name . (Change the id at end of PUT url to match the id of the created customer)
curl -i -u admin02@tw.com:admin02@123# -X PUT -H "Content-Type: application/json" http://localhost:8080/customer/1 -d '{"firstName":"John","lastName":"Doe01","emailAddress":"johndoe@gmail.com","addresses":[{"streetAddress":"4487 Elsie Drive","city":"Onida","stateCode":"SD","zipCode":"57564","country":"USA"}]}'
Go to database and run sql – select * from customer where email_address = 'johndoe@gmail.com'

3.0 Hibernate Envers Auditing
Although JPA Auditing provided easy configuration for basic auditing, it only provided following information
- When was an entity created and who created it.
- When was the entity last modified and who modified it.
It does not provides you with details of all the changes/updates that were done for an entity. e.g a Customer entity could have been modified 5 times. With JPA auditing, there is no way i can find out what was changed in the entity in each of the 5 updates, and who did them
And so Hibernate Envers
comes into the picture, and it provides complete Audit History for an entity.
3.1 How does Hibernate Envers stores audits in database
- Envers creates a
REVINFO
table- This table is common across all entities in the application.
- It contains details of when each revision of entity was done and by whom
- By default it contains two columns
REV
– Primary Key Column – Revision ID/ Revision NumberREVTSTMP
– Timestamp of when Revision was created.- Additionally we can also store user information also in this table in a custom column
- Basically, Envers creates an ‘AUDIT‘ table for each entity that needs to be audited.
- e.g if we want to audit the changes to
CUSTOMER
table, Envers will create store the audit history in aCUSTOMER_AUD
table. (Name of the table is suffixed with_AUD
) - This
*_AUD
table has all the columns as the main entity table. - In addition it also has a two other columns
REV
: Revision ID – Foriegn key Reference to theREVINFO.REV
REVTYPE
: Has three possible numeric values to denote the type of operation on entity –0
(Add),1
(Update), and2
(Delete)
- e.g if we want to audit the changes to
Hibernate provides some customizations to the name of the Enver tables and columns. We will do following customizations in our application for better readability of table and column names
- The table Suffix
_AUD
will be changed to_AUDIT
. e.g the audit table forCUSTOMER
will be nowCUSTOMER_AUDIT
. Following columns of all the audit tables will also be renamedREV
column will be renamed toREVISION_ID
REVTYPE
column will be rename toREVISION_TYPE
REVINFO
table will be renamed toREVISION_INFO
. Additionally following columns will be renamed in the revision tableREV
column will be renamed toREVISION_ID
REVTSTMP
will be renamed toREV_TIMESTAMP
To Summarize these is how the ER Diagram of AUDIT tables will look like.

You might have noticed that there is NO Foriegn key reference from audit tables to main entity tables for ID column.
The reason for this is that in some scenarios we may want to keep Deleted records also in the Audit tables. In such scenarios the foriegn key reference won’t work as records in main entity table will have been deleted.
3.2 Add auditing tables into the database
We have already seen how the audit tables and revision info tables will look like. Lets create those tables in the database.
You can either run the below script directly on your database, or you can add it to your Flyway/Liquibase scripts (in case you are using them or any other such database versioning tools).
CREATE TABLE `revision_info` (
`revision_id` INTEGER PRIMARY KEY AUTO_INCREMENT,
`rev_timestamp` BIGINT(20) NOT NULL,
`user` VARCHAR(50) NOT NULL
);
CREATE TABLE `customer_audit` (
`revision_id` INTEGER NOT NULL,
`id` BIGINT(20) NOT NULL,
`revision_type` TINYINT NOT NULL,
`email_address` VARCHAR(255),
`first_name` VARCHAR(255),
`last_name` VARCHAR(255),
`created_by` VARCHAR(50),
`updated_by` VARCHAR(50),
`created_on` DATETIME,
`updated_on` DATETIME,
PRIMARY KEY (`revision_id`, `id`),
CONSTRAINT `idfk_customer_revinfo_rev_id`
FOREIGN KEY (`revision_id`) REFERENCES `revision_info` (`revision_id`)
);
CREATE TABLE `address_audit` (
`revision_id` INTEGER NOT NULL,
`id` BIGINT(20) NOT NULL,
`revision_type` TINYINT NOT NULL,
`city` VARCHAR(255),
`country` VARCHAR(255),
`customer_id` BIGINT(20),
`state_code` VARCHAR(255),
`street_address` VARCHAR(255),
`zip_code` VARCHAR(255),
PRIMARY KEY (`revision_id`, `id`),
CONSTRAINT `idfk_address_revinfo_rev_id`
FOREIGN KEY (`revision_id`) REFERENCES `revision_info` (`revision_id`)
);
3.3 Enable Auditing
In this section, we will add auditing by fail first method.
This means we will add configurations/code one step at a time, start the application after that and see if application starts or fails. If it fails, we will fix it and repeat the cycle again till we get a working application.
This will help us understand why some configurations/code is required and what errors may come if we miss them.
3.3.1 Add dependency to build.gradle
The first configuration to be done is to add the Hibernate Envers
dependency in build.gradle
compile('org.hibernate:hibernate-envers:5.4.25.Final')
3.3.2 Add @Audited annotation
Now add the @Audited
annotation to all the entities that you want to audit
@Audited
public class Customer {
....
}
@Audited
public class Address {
....
}
3.3.3 Add configurations to application.yaml
Let’s try to start the application.
You will get following error
org.hibernate.tool.schema.spi.SchemaManagementException: Schema-validation: missing table [address_aud]
OR
org.hibernate.tool.schema.spi.SchemaManagementException: Schema-validation: missing table [customer_aud]
This is because Hibernate Envers is expecting Audit tables as *_AUD
table.
However as you have seen, we have customized our table suffix as _AUDIT
table. This means Envers is search for customer_aud table
, address_aud
table, but instead we have customer_audit
and address_audit
table. Additonally we had also customized the columns – REV
, REVTSMTP
and REVTYPE
.
The failures are because we need to tell Hibernate Envers about these customizations. We can do so by adding the following properties in application.yaml
spring:
jpa:
properties:
org:
hibernate:
envers:
audit_table_suffix: _AUDIT
revision_field_name: REVISION_ID
revision_type_field_name: REVISION_TYPE
Now if you again try starting the application, you will get the below error
Schema-validation: missing table [customer_address_audit]
To Fix this error, add a @NotAudited
annotation to the field addresses
in the Customer
entity class
@NotAudited
@OneToMany(fetch = FetchType.EAGER, orphanRemoval = true, targetEntity = Address.class, cascade = CascadeType.ALL)
@JoinColumn(name = "customer_id")
private List<Address> addresses;
3.3.4 Configure a RevisionEntity
Let’s try to start the application again.
This time you will get following error.
org.hibernate.tool.schema.spi.SchemaManagementException: Schema-validation: missing table [revinfo]
We have created a table REVISION_INFO
instead of REVINFO
.
However we cannot change the name of table via configurations in application.yaml
. To do this we need to create a RevisionEntity
class.
Envers
already provides a class – org.hibernate.envers.DefaultRevisionEntity
, and we will extend this class.
@Entity
@Table(name = "revision_info")
@RevisionEntity
public class AuditRevisionEntity extends DefaultRevisionEntity {
@Column(name = "user")
private String user;
}
- We created a class
AuditRevisionEntity
which extends fromDefaultRevisionEntity
. - Add
@Table
annotation and provide name of table –revision_info
- Add annotation
@RevisionEntity
, which signifies that this is theRevisionInfo
Entity for Hibernate Envers. - Add a field for
user
, which we had added in therevison_info
table.
Let’s try to start the application again. We will get following error this time around.
org.hibernate.tool.schema.spi.SchemaManagementException: Schema-validation: missing table [hibernate_sequence]
- Hibernate requires this for ID columns which have following annotation –
@GeneratedValue(strategy = GenerationType.AUTO)
- Easiest way to solve this ito change the Generation Strategy from
GenerationType.AUTO
toGenerationType.IDENTITY
- However, this error is because of ID field in
org.hibernate.envers.DefaultRevisionEntity
, which we extended.
Since we cannot change DefaultRevisionEntity class, we will add below configuration in application.yaml to fix this.
spring:
jpa:
hibernate:
use-new-id-generator-mappings: false
Let’s try starting the application again.
This time you will get following error, because Envers expects id
and timestamp
columns in the revision_info table.
Schema-validation: missing column [id] in table [revision_info]
Since we have renamed the columns to revision_id
and rev_timestamp
, we need to add configurations in our RevisionEntity
class.
Just add following annotations to the AuditRevisionEntity
class.
@AttributeOverrides({
@AttributeOverride(name = "timestamp", column = @Column(name = "rev_timestamp")),
@AttributeOverride(name = "id", column = @Column(name = "revision_id"))
})
public class AuditRevisionEntity extends DefaultRevisionEntity {
...
...
}
Now you should be able to start your application successfully.
Now you know what all different errors that came possibly come when you configure Envers auditing, and how to fix them.
Note: There is no need to add
spring-data-envers
library into your project. Hibernate Envers library is more than enough.
3.4 Populating Logged In User Info In Revision table
Now we have everything setup, and our application is also running.
Lets try to create a new customer.
curl -i -u admin01@tw.com:admin01@123# -X POST -H "Content-Type: application/json" http://localhost:8080/customer/ -d '{"firstName":"John","lastName":"Doe","emailAddress":"johndoe@gmail.com","addresses":[{"streetAddress":"4487 Elsie Drive","city":"Onida","stateCode":"SD","zipCode":"57564","country":"USA"}]}'
You will get following error
Caused by: java.sql.SQLException: Field 'user' doesn't have a default value
This happens, because Envers is trying to audit the data into revision_info
table, and puts null
into the user
column.
This is because Envers does not know how to get the logged in user information and populate in this user
column.
Let’s add code to add logged in user information.
- Create a class that implements
org.hibernate.envers.RevisionListener
- In the new class implement the method –
public void newRevision(Object revisionEntity)
- In this method get the current user from Spring’s
SecurityContext
(similar to what we did inAuditorAwareImpl
) - And set this user in
AuditRevisionEntity
- In this method get the current user from Spring’s
public class AuditRevisionListener implements RevisionListener {
@Override
public void newRevision(Object revisionEntity) {
String currentUser = Optional.ofNullable(SecurityContextHolder.getContext())
.map(SecurityContext::getAuthentication)
.filter(Authentication::isAuthenticated)
.map(Authentication::getPrincipal)
.map(User.class::cast)
.map(User::getUsername)
.orElse("Unknown-User");
AuditRevisionEntity audit = (AuditRevisionEntity) revisionEntity;
audit.setUser(currentUser);
}
}
Next add the following annotation to the RevisionEntity
class – AuditRevisionEntity
@RevisionEntity(AuditRevisionListener.class)
3.5 Testing Hibernate Envers Auditing
Now let’s test the application and see if auditing is working correctly.
Login as and user (admin01@tw.com) and create a customer using below command
curl -i -u admin01@tw.com:admin01@123# -X POST -H "Content-Type: application/json" http://localhost:8080/customer/ -d '{"firstName":"John","lastName":"Doe","emailAddress":"johndoe@gmail.com","addresses":[{"streetAddress":"4487 Elsie Drive","city":"Onida","stateCode":"SD","zipCode":"57564","country":"USA"}, {"streetAddress":"2240 Bridge Parkway","city":"San Francisco","stateCode":"CA","zipCode":"94065","country":"USA"}]}'
Now login as another user (admin02@tw.com) update the same customer, and change last name and remove one of the address . (Change the id at end of PUT url to match the id of the created customer)
curl -i -u admin02@tw.com:admin02@123# -X PUT -H "Content-Type: application/json" http://localhost:8080/customer/1 -d '{"firstName":"John","lastName":"Doe01","emailAddress":"johndoe@gmail.com","addresses":[{"id":1, "streetAddress":"4487 Elsie Drive","city":"Onida","stateCode":"SD","zipCode":"57564","country":"USA"}]}'
Now let’s go to database and verify the audit history got populated correctly.
Run sql – select * from revision_info;
This will give you information on how many revisions were created and by whom.

Next look at the customer audit table – select * from customer_audit

You can see above that in Revision 1, the customer John Doe was created, and Revision 2, it’s last name was updated.
Finally let’s look at Address Audit table – select * from address_audit;

We can see from above that in Revision 1, two addresses were added. And in Revision 2, one of the address got deleted.
Deleted entries are stored in audit table as
NULL
values.
If you want to keep the old values instead ofNULL
, then set following property to true inapplication.yaml
org.hibernate.envers.store_data_at_delete: true
3.6 Fetching Auditing information from the application via REST API
3.6.1 Create a bean of AuditReader
Hibernate Envers provides two methods to fetch the audit information for an entity.
forRevisionsOfEntity
– This is used to fetch all revision changes that an entity has went through.forRevisionsOfEntityWithChanges
– This is to fetch revision changes along with information on which fields and data was changed in each revision.
You will need an instance of class org.hibernate.envers.AuditReader
, to create AuditQuery
that will be used to fetch audit information
Add following configuration class to create a bean of org.hibernate.envers.AuditReader
@Configuration
public class AuditConfiguration {
private final EntityManagerFactory entityManagerFactory;
AuditConfiguration(EntityManagerFactory entityManagerFactory) {
this.entityManagerFactory = entityManagerFactory;
}
@Bean
AuditReader auditReader() {
return AuditReaderFactory.get(entityManagerFactory.createEntityManager());
}
}
3.6.2 Create a method in Customer Service to fetch Customer audits
Now that we have create a bean of AuditReader
, go ahead and autowire the AuditReader dependency into the CustomerService
class
public class CustomerService {
...
...
@Autowired
private AuditReader auditReader;
...
}
Now add a method in CustomerService
, to fetch audit revision details for customer entity
public List<?> getRevisions(Long id, boolean fetchChanges) {
AuditQuery auditQuery = null;
if(fetchChanges) {
auditQuery = auditReader.createQuery()
.forRevisionsOfEntityWithChanges(Customer.class, true);
}
else {
auditQuery = auditReader.createQuery()
.forRevisionsOfEntity(Customer.class, true);
}
auditQuery.add(AuditEntity.id().eq(id));
return auditQuery.getResultList();
}
This method will fetch only revisions if fetchChanges
boolean is false, else it will return all revisions with details of what has been changed.
3.6.3 Expose an Rest API for Customer Audits
Expose a Rest API like this
@GetMapping(path = "/{id}/revisions", produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<?> getRevisions(@PathVariable(name = "id") String customerId,
@RequestParam(value = "fetchChanges", required = false) boolean fetchChanges) {
List results = customerService.getRevisions(Long.valueOf(customerId), fetchChanges);
return ResponseEntity.ok(results);
}
Now lets hit the APIs to fetch the audit information. Hit the below URL to get audit revisions for customer Id 2.
curl -i -u admin02@tw.com:admin02@123# \
-X GET -H "Content-Type: application/json" \
http://localhost:8080/customer/2/revisions
You will get below error, as application is not able to serialize the AuditRevisionEntity
com.fasterxml.jackson.databind.exc.InvalidDefinitionException: No serializer found for class org.hibernate.proxy.pojo.bytebuddy.ByteBuddyInterceptor and no properties discovered to create BeanSerializer
To fix this, add following annotation to AuditRevisionEntity class
@JsonIgnoreProperties({"hibernateLazyInitializer", "handler"})
public class AuditRevisionEntity extends DefaultRevisionEntity {
....
}
Now if you hit the same URL back again, you should get list of revisions for customer with id = 2 (as shown below)
[
{
"id": 1,
"timestamp": 1608084027455,
"user": "admin01@tw.com",
"revisionDate": "2020-12-16T02:00:27.455+0000"
},
{
"id": 2,
"timestamp": 1608084069397,
"user": "admin02@tw.com",
"revisionDate": "2020-12-16T02:01:09.397+0000"
}
]
3.6.4 Fetching audits with change details
In above example we only got information about when the object was changed. We did not get information of who changed it.
To do that hit the same URL with query param fetchChanges=true
curl -i -u admin02@tw.com:admin02@123# \
-X GET -H "Content-Type: application/json" \
http://localhost:8080/customer/2/revisions\?fetchChanges\=true
Unfortunately you will get following error
{
"statusCode": 400,
"errorMessage": "The specified entity [com.dev.springdemo.customer.model.Customer] does not support or use modified flags."
}
This is because we have not configured our Customer Entity to track which fields were changed.
Modify the @Audited
annotation on Customer
Entity , and add property withModifiedFlag = true
@Audited(withModifiedFlag = true)
public class Customer implements Serializable {
...
}
Now if you re-run the code, you will again get error (something like below)
Schema-validation: missing column [email_address_mod] in table [customer_audit]
Envers can track field changes by adding
_mod
column for every entity field in *_audit
table.So for every field of customer, we need to add a corresponding
boolean
column incustomer_audit
table and suffix the column name with_mod
E.g. for email_address
column in customer
table, add a email_address_mod
field in the customer_audit
table.
So for adding all *_mod
columns in customer_audit
table, run following sql script.
ALTER TABLE `customer_audit`
ADD COLUMN `created_by_mod` TINYINT(1) NULL,
ADD COLUMN `created_on_mod` TINYINT(1) NULL ,
ADD COLUMN `email_address_mod` boolean NULL,
ADD COLUMN `first_name_mod` boolean NULL,
ADD COLUMN `last_name_mod` boolean NULL,
ADD COLUMN `updated_by_mod` boolean NULL,
ADD COLUMN `updated_on_mod` boolean NULL;
Now we are all set to test it out finally. Create some new audit entries by creating and updating the customers.
Now if you fetch revision with chagnes, you will get an output something like this.
[
[
{
"id": 4,
"firstName": "Clark",
"lastName": "Kent",
"emailAddress": "superman@gmail.com",
"addresses": null,
"createdBy": "admin01@tw.com",
"updatedBy": "admin01@tw.com",
"createdOn": "2020-12-16T03:25:01.000+0000",
"updatedOn": "2020-12-16T03:25:01.000+0000"
},
{
"id": 3,
"timestamp": 1608089100811,
"user": "admin01@tw.com",
"revisionDate": "2020-12-16T03:25:00.811+0000"
},
"ADD",
[]
],
[
{
"id": 4,
"firstName": "Super",
"lastName": "Man",
"emailAddress": "superman@gmail.com",
"addresses": null,
"createdBy": "admin01@tw.com",
"updatedBy": "admin02@tw.com",
"createdOn": "2020-12-16T03:25:01.000+0000",
"updatedOn": "2020-12-16T03:25:49.000+0000"
},
{
"id": 4,
"timestamp": 1608089148899,
"user": "admin02@tw.com",
"revisionDate": "2020-12-16T03:25:48.899+0000"
},
"MOD",
[
"firstName",
"lastName",
"updatedBy",
"updatedOn"
]
]
]
The entire code of for this project can be found at – https://github.com/chatterjeesunit/spring-boot-app/tree/v4.0
To checkout this Release tag, run following command.
git clone https://github.com/chatterjeesunit/spring-boot-app.git
cd spring-boot-app
git checkout tags/v4.0 -b v4.0-with-auditing
This will bring us to the end of this blog.
In future articles we will look into how to add caching, and other features to a Spring boot application.
Hi, thanks for the tutorial. I am getting following error in my project:
2020-12-13 00:22:29.418 ERROR 80983 — [ost-startStop-1] o.s.b.web.embedded.tomcat.TomcatStarter : Error starting Tomcat context. Exception: org.springframework.beans.factory.UnsatisfiedDependencyException. Message: Error creating bean with name ‘webSecurityConfig’: Unsatisfied dependency expressed through field ‘userDetailsService’; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name ‘userDetailsServiceImpl’: Unsatisfied dependency expressed through field ‘userRepository’; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name ‘userRepository’: Cannot create inner bean ‘(inner bean)#4384acd’ of type [org.springframework.orm.jpa.SharedEntityManagerCreator] while setting bean property ‘entityManager’; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name ‘(inner bean)#4384acd’: Cannot resolve reference to bean ‘entityManagerFactory’ while setting constructor argument; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name ‘entityManagerFactory’ defined in class path resource [org/springframework/boot/autoconfigure/orm/jpa/HibernateJpaConfiguration.class]: Invocation of init method failed; nested exception is java.lang.NoClassDefFoundError: org/hibernate/resource/beans/spi/ManagedBeanRegistry
Coud you please help?
LikeLike
can you share your code on github, so that I can try taking a look
LikeLike
Hi, thanks for the reply. I have added some details here:
https://stackoverflow.com/questions/65270930/error-when-adding-custom-revision-in-hibernate-envers
LikeLike
Can I delete a revision from the revision_info table and the corresponding row from the audit table? how do you do that?
LikeLike
Hi Pietro,
Ideally audit entries are required for compliance purpose. Hence audit entries are considered immutable, i.e. once created they should not be either edited or deleted.
I have never explored deletion of audit entries, and I am not sure if Envers exposes any API to do so. (of course you can always go and delete directly from the database.)
I am more curious though on why do you want to delete audit entries?
LikeLike
Hi Sunit – Awesome article, thank you for sharing. I have a question – I see a revision_info_audit table is created also, which seems redundant. Are you aware of a way to prevent that table from being created? Thanks.
LikeLike
When i had implemented this, there was no revision_info_audit table created.
I had `hibernate.ddl_auto` set to `validated` so no tables were created automatically for me.
I am assuming you have this value set to `create` or `update`, hence tables are automatically created for you.
Can you check if you have @Audited annotation on the RevisionEntity class, and if yes then try removing it
LikeLike
Hi Sunit, I am trying to audit two entities both are related with a mapping. Suppose, according to your example I want to audit Address also. Then what is happening to me is, whenever I am trying to insert new Customer with multiple addresses then in customer’s audit table I am getting two audit entries, one for customer’s insertion and second one for customer’s update only if I use “@Audited(targetAuditMode = RelationTargetAuditMode.NOT_AUDITED) or @NOTAUDITED” on customer’s list of address.
And when I am using “AuditMappedBy “, Suppose I am inserting new customer with 3 addresses it will add 3-4 audit entries in customer audit for each address.
Can you please post something, on how to use auditing with mappings in entities. I am too confused how to handle these mappings with auditing. I have too much data to insert. I can’t afford repeatetion of data. Please update as soon as possible.
LikeLike
Hi Nidhi I tried the same again today on my latest code – inserted a single customer with 3 addresses. And i confirmed that single audit entry is created for customer and 3 audit entries are getting created for the addresses. I dont think that its an issue with the way audits are configured, but could be an issue in the way you are saving the customer. I am saving the customer just by doing – customerRepository.save(customer), and it saves both the customer and address in a single database transaction. You can check the latest code at – https://github.com/chatterjeesunit/spring-boot-app
LikeLike
Is there someway you can share your sample code? If yes then i can take a look at your code and also try to run at my end and find out why multiple audits are getting created for you
LikeLike
Hello,
Thanks for this article 🙂
I implemented this solution but I had this error even after I added the right annotation !
InvalidDefinitionException: No serializer found for class org.hibernate.proxy.pojo.bytebuddy.ByteBuddyInterceptor and no properties discovered to create BeanSerializer
I debugged my code and I saw that the revisions (ArrayList) is loaded just before returning the results to the Postman request.
Any thoughts ?
LikeLike
Are you getting this error while saving the object OR when you are trying to retrieve the audits?
Can you share your sample code or github repository if possible? I can try to take a look at your code and see what is the issue.
LikeLike
Hello,
Thank you for replying 🙂
I’m getting this error when I retrieve audits.
LikeLike
Hi,
In the blog i have covered the solution for the issue where you get serialization error in fetching audits.
Did you try adding the `@JsonIgnoreProperties({“hibernateLazyInitializer”, “handler”})` annotation to the `AuditRevisionEntity` class?
Can you please share your code in github if possible, so that I can take a look and check what is the issue? I dont know what else could be causing this unless i look at the code
LikeLike
Hi,
I found the solution to my problem (when retrieving audits) :
InvalidDefinitionException: No serializer found for class org.hibernate.proxy.pojo.bytebuddy.ByteBuddyInterceptor and no properties discovered to create BeanSerializer
It’s not only adding this annotation :
`@JsonIgnoreProperties({“hibernateLazyInitializer”, “handler”})` annotation to the `AuditRevisionEntity` class
The problem is with empty (NULL) values when returning audits via Spring MVC that uses JSON serializers that throws the exception :
`com.fasterxml.jackson.databind.exc.InvalidDefinitionException: No serializer found for class org.hibernate.proxy.pojo.bytebuddy.ByteBuddyInterceptor and no properties discovered to create BeanSerializer (to avoid exception, disable
SerializationFeature.FAIL_ON_EMPTY_BEANS) (through reference chain: WHLive.messages.EditSkillResponse[“skills”]->java.util.ArrayList[0]->WHLive.model.Skill$HibernateProxy$PvDfTPV7[“hibernateLazyInitializer”])`
The solution is very simple (and funny) :
Add this annotation `@JsonInclude(JsonInclude.Include.NON_EMPTY)` to the `AuditRevisionEntity` class.
Best regards
LikeLiked by 1 person
Should these be used as history tables for users? What happens when these get into billions of rows?
LikeLike
I would like to clarify that users table was just an example to demonstrate the concept.
I am not suggesting that users table should be designed in this way in every production system.
Its upto the system designers to decide whats best for their system depending upon the amount of data/ access patterns, etc.
On a side note, even if there are billion of records, how much of that data is recent and is actually accessed on daily basis?
Can old data be archived ? Can you do database table partitioning or database sharding to divide the data?
There is no single solution that can apply to all systems. It is upto you to analyze your system requirements, and adapt the design of your system as per your system requirements
LikeLike
Oh, I wasn’t knocking your example. I was just talking in general terms about allowing users to peruse those tables as history.
LikeLike
Hi I have a problem when using envers when using @Transactional, could you help me pls?
in my service I save many attachment data.
I saw in the saving log envers only called hibernate_sequence once once also saving to revision_info only one time, but the data more than 1 so my code error cause of duplicate data .
Here my log:
2022-11-14 15:14:18.150 : select nextval (‘hibernate_sequence’)
2022-11-14 15:14:18.178 : insert into revision_info (timestamp, username, revision_id) values (?, ?, ?)
insert first data:
2022-11-14 15:14:18.205 : insert into m_attachment_aud (revtype, changed_by, changed_dt, checksum, created_by, created_dt, document_type, filename, masa_berlaku_from, masa_berlaku_to, nomor_vema, upload_by, version, document_no, rev) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
insert second data:
2022-11-14 15:14:18.231 : insert into m_attachment_aud (revtype, changed_by, changed_dt, checksum, created_by, created_dt, document_type, filename, masa_berlaku_from, masa_berlaku_to, nomor_vema, upload_by, version, document_no, rev) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
The error i got:
Caused by: org.postgresql.util.PSQLException: ERROR: duplicate key value violates unique constraint “m_attachment_aud_pk”
Detail: Key (rev)=(60) already exists.
LikeLike
What is the primary key of the table “m_attachment_aud”?
Generally for any one transaction there is only one revision id created.
All the child audit records have the same “revision_id”
Ideally the audit table should have a primary key as (revision_id, id) – where id is the id of the attachment in your example.
In this way you should not get duplicate key error, as the attachment id will be different for each attachment.
LikeLike