Skip to content

Temporal auditing extension of the Spring Data JPA module

License

Notifications You must be signed in to change notification settings

ClaudioConsolmagno/spring-data-jpa-temporal

Repository files navigation

Spring Data JPA Temporal Audit

Maven Central javadoc

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.

Who is this for?

  • 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 have a relatively simple model and can live with the limitations of this library described in the Limitations section below.

Usage

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);

Alternatives

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.

Limitations

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.

Next Steps

  • 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)

License

Spring Data JPA Temporal Audit is Open Source software released under the Apache 2.0 license.