Additional auditing with Spring Data JPA and Hibernate
By Sascha Boeing
Takes 3 minutes to read
Spring Data provides an easy way of keeping track who creates and modifies a persistent entity as well as when the action happened by annotating properties with @CreatedBy, @CreatedDate, @LastModifiedBy and @LastModifiedDate. The properties are automatically provided by an implementation of the AuditAware and DateTimeProvider interface.
Beside this common auditing information in my current projects some entities require storing auditing-information about crucial state-changes like soft-deletion.
Previously we had to fill the properties ourself:
This is of course not a big thing but anyway: Why do we have to do it? Why can’t we simply use the same ‘magic’ that is working for the regular auditing properties?
So the simplest approach is to use @PreUpdate to fill the additional auditing properties. To avoid updating them on each update of our entity we use a transient boolean.
This has just one problem: @PreUpdate is only called when something on the entity has been changed and an update is necessary. But since markedForDeletion is transient changing its value does not trigger an update.
A quite simple solution is to just change ‘something’ on the entity together with marking it for deletion. To guarantee triggering a PreUpdate while not having additional unnecessary persistent properties we can either change the deletedBy or deletedOn property to some temporary value. The @PreUpdate method will override these values with correct values eventually.
A more complex solution is telling Hibernate that the entity has been updated without unnecessarily changing properties. Hibernate facilitates that by supporting a CustomEntityDirtinessStrategy. But this would require us to check the full entity including Collections and other complex property types. We definitely don’t want that!
Instead, we can create a CompositeUserType for our additional auditing properties. CompositeUserTypes are checked for dirtiness by calling an equals-method, comparing their current instance with the originally loaded snapshot.
So both solutions have their drawbacks. On the one hand it is very technical and not domain-driven to have these temporary values. On the other it’s a tad on the over-engineering side to provide a CompositeUserType for something as simple as 2 auditing-fields.
Anyways, there are examples enough for those auditing-requirements. Examples include by whom or when some some email was sent, a file was downloaded or that process was triggered.
So which solution do you prefer?
Personally I would choose the complex one as it is cleaner.
After studying computer science in Ravensburg Sascha worked several years on online games as a backend developer. Recently his focus is on developing custom software for Sport Alliance GmbH in Hamburg.