Sample application that demonstrates entity revisions with Spring Data Envers.
Spring Data Jpa provides rough audit information. However, if you are looking for what are the exact changes being made to an entity you can do so with Spring Data Envers.
As the name has suggested Spring Data Envers utilises and simplifies the usage of Hibernate Envers.
In order to enable Envers features we will first include spring-data-envers as dependency.
implementation 'org.springframework.data:spring-data-envers'
By annotating an @Entity
with @Audited
, we are informing Spring that we would like respective entity to be audited.
The following example shows that we want all activities related to Book to be audited:
@Entity
@Audited
public class Book {
@Id
@GeneratedValue
private Long id;
private String author;
private String title;
}
Next is to extend a Repository
class in order to allow us to utilise audit revision features. This can be done by extending
RevisionRepository interface to our Repository
class. An example can be seen in BookRepository:
public interface BookRepository extends JpaRepository<Book, Long>, RevisionRepository<Book, Long, Integer> {
}
We will be utilising on @SpringBootTest
to verify that our implementation works.
@Testcontainers
@SpringBootTest(properties = "spring.jpa.hibernate.ddl-auto=create-drop")
class BookAuditRevisionTests {
@Container
@ServiceConnection
private static final MySQLContainer<?> MYSQL = new MySQLContainer<>("mysql:lts");
@Autowired
private BookRepository repository;
@Test
@DisplayName("When a book is created, then a revision information is available with revision number 1")
void create() {
var book = new Book();
book.setTitle("The Jungle Book");
book.setAuthor("Rudyard Kipling");
var createdBook = repository.save(book);
var revisions = repository.findRevisions(createdBook.getId());
assertThat(revisions)
.hasSize(1)
.first()
.extracting(Revision::getRevisionNumber)
.returns(1, Optional::get);
}
}
@Testcontainers
@SpringBootTest(properties = "spring.jpa.hibernate.ddl-auto=create-drop")
class BookAuditRevisionTests {
@Container
@ServiceConnection
private static final MySQLContainer<?> MYSQL = new MySQLContainer<>("mysql:lts");
@Autowired
private BookRepository repository;
@Test
@DisplayName("When a book is modified, then a revision number will increase")
void modify() {
var book = new Book();
book.setTitle("The Jungle Book");
book.setAuthor("Rudyard Kipling");
var createdBook = repository.save(book);
createdBook.setTitle("If");
repository.save(createdBook);
var revisions = repository.findRevisions(createdBook.getId());
assertThat(revisions)
.hasSize(2)
.last()
.extracting(Revision::getRevisionNumber)
.extracting(Optional::get).is(matching(greaterThan(1)));
}
}
@Testcontainers
@SpringBootTest(properties = "spring.jpa.hibernate.ddl-auto=create-drop")
class BookAuditRevisionTests {
@Container
@ServiceConnection
private static final MySQLContainer<?> MYSQL = new MySQLContainer<>("mysql:lts");
@Autowired
private BookRepository repository;
@Test
@DisplayName("When a book is removed, then only ID information is available")
void remove() {
var book = new Book();
book.setTitle("The Jungle Book");
book.setAuthor("Rudyard Kipling");
var createdBook = repository.save(book);
repository.delete(createdBook);
var revision = repository.findLastChangeRevision(createdBook.getId());
assertThat(revision).get()
.extracting(Revision::getEntity)
.extracting("id", "title", "author")
.containsOnly(createdBook.getId(), null, null);
}
}
All tests above can be found in BookAuditRevisionTests.