Spring Data JPA Temporal Audit is an extension of spring-data-jpa that makes it simple to keep an audit of your data in the same table as your main data itself.
It does that by implementing temporal database functionality completely on the application side, so it works with any DB engines that JPA integrates with using minimal configuration.
More specifically, your table becomes a "system-version table". The following excerpt is from mariadb:
System-Versioned Tables
Normally, when you issue a statement that updates a row on a table, the new values replace the old values on the row so that only the most current data remains available to the application.
With system-versioned tables, [the db server] tracks the points in time when rows change. When you update a row on these tables, it creates a new row to display as current without removing the old data. This tracking remains transparent to the application. When querying a system-versioned table, you can retrieve either the most current values for every row or the historic values available at a given point in time.
You may find this feature useful in efficiently tracking the time of changes to continuously-monitored values that do not change frequently, such as changes in temperature over the course of a year. System versioning is often useful for auditing.
- You want a simple and lightweight auditing mechanism for your tables, i.e.:
- No other tables are created
- No triggers
- Adding a new column automatically starts versioning it
- Minimal springboot configuration
- You want a no fuss way of looking at audit:
- You can very easily use regular
findBy
methods to find data at a point in time - You can use Spring's
RevisionRepository
interface to find all revisions associated to a record - At a database level, everything is in the same table as your main data itself, so it's very easy to analyse, report and build upon that data. For example:
select * from employee where id = 1 and to_date > now() -- finds employee with id == 1 as of right now (current data) select * from employee where id = 1 and to_date > '2021-04-01T00:00:00.000Z' and from_date <= '2021-04-01T00:00:00.000Z'-- finds employee with id == 1 as of 2021-04-01T00:00:00.000Z select * from employee where id = 1 order by from_date -- find all revisions of employee with id == 1
- You can very easily use regular
- You have a relatively simple model and can live with the limitations of this library described in the Limitations section below.
Look at the latest release ${version}
in github and add a dependency to your build file:
Maven:
<dependency>
<groupId>dev.claudio</groupId>
<artifactId>spring-data-jpa-temporal</artifactId>
<version>${version}</version>
</dependency>
Gradle:
implementation 'dev.claudio:spring-data-jpa-temporal:${version}'
As a quick-start, take a look at the sample springboot application in the src/test/java directory. It shows the simplest usage of this extension. For extra information and alternative usage here's a step-by-step summary of what you need:
Spring main class (e.g. SpringDataJpaTemporalApplication.java)
Use @EnableJpaTemporalRepositories
(see SpringDataJpaTemporalApplication.java
).
This makes this extension work, and that it only works on repositories that extend TemporalRepository.java
(see below).
Alternatively, you can use @EnableJpaRepositories(repositoryFactoryBeanClass = DefaultRepositoryFactoryBean.class)
if you need to configure something else for your regular JPA repositories.
Entity (e.g. Employee.java)
From your domain class (e.g. Employee.java
), extend Temporal.java
. Alternatively, use annotations @TemporalId
, @FromDate
and @ToDate
in fields of your class.
@TemporalId
must be your primary key so it needs to have @Id
and @GeneratedValue
on the same field.
Use @UniqueKey
on your unique key attribute in your entity (e.g. employee_id
).
If you are using Lombok and are extending Temporal.java
mark your entity with @EqualsAndHashCode(callSuper = false)
.
If not, make sure your equals and hashcode implementations don't use any of the values marked with @TemporalId
, @FromDate
and @ToDate
.
Repository (e.g. Repository.java)
Create a repository interface that extends TemporalRepository<T,ID>
. T
is your entity and ID
is the type of your unique key (marked with @UniqueKey
).
Example extends TemporalRepository<Employee, Integer>
Database schema (e.g. db.sql)
For better query performance create a unique index on your @UniqueKey
and @ToDate
columns. E.g. create unique index employee_id_to_date_index on employee (employee_id, to_date);
I'm not aware of any other "temporal" JPA implementations although there are plenty of regular auditing libraries, Javers being my favourite of those:
- Javers
- Envers
- A custom implementation with
@PrePersist
,@PreUpdate
, etc. - Triggers on the database (please don't...)
- A native temporal implementation to your database engine (e.g. mariadb). This is a nice auditing solution, it's basically what this project does but at the DB level. This means it's not portable, you'll need to configure things manually and create manual audit finder methods.
The following 2 functionalities aren't currently supported with this library. An exception may be thrown at spring boot start-up if you try to use them. I'll try and work on those in the future.
- Does not support derived query methods, e.g.
findByNameAndAddress
,countByNameAndAddress
, etc. However, you could create methods and use@Query
annotation to specify a query to run (e.g. Repository.java). - Does not support relations, e.g.
@OneToOne
,@OneToMany
, etc.
- Java 9 modules?
- Add debug/trace logging
- Work on limitations (section above)
- Maybe listeners can help solve relations limitation (e.g. see EnversPreUpdateEventListenerImpl)
- Implement
findRevisions(ID, Pageable)
- Mongodb support (separate library, same concept)
Spring Data JPA Temporal Audit is Open Source software released under the Apache 2.0 license.