A lot of Customer want to store some Metadata to an entry in the database. The most common are
- When the entry was created (createdDate)
- When the entry was lastly updated (lastModifiedDate)
- Who created the entry (createdBy)
- Who modified the entry lately (lastModifiedBy)
Page Contents
One way handling this different things is by performing the actions manually. Everytime you write in the database you could intercept, get the entry add the metadata to this and write the entry to your database (again).
That´s a well known way but I think it isn´t a very comfortable way.
In the following Post we will look at how to achieve this without writing a bunch of code or have to intercept in an transaction.
Preconditions
Before we start with our intercepting please make sure you have a Spring Boot Application that you can compile and run. If not, may have a look at this.
Maven Dependencies
Furthermore make sure you have the following dependencies added to your pom.xml.
<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.0.3.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> .... <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> .... <dependency> <groupId>com.h2database</groupId> <artifactId>h2</artifactId> <scope>runtime</scope> </dependency> .... </dependencies>
If you have an application with Rest API you have to add some more dependencies. But this is just a hint, for our example you don´t have to.
Hero Entity
This example will work with the an entity called hero. Nothing special about it (for now). Just define it like this
import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; /** * * @author javadevcorner.com */ @Entity public class Hero { @Id @GeneratedValue(strategy = GenerationType.AUTO) private long id; @Column(name = "HERO_NAME") private String heroName; //getter and setter }
Implementing
Now we start with the implementation of our Metadata Handler.
Generic Metadata Class
First of all we have to define an entity that contains the metadata we want to have.
Because we don´t want to implement the the metadata over and over again we´ll define them in an abstract class. This one can be extend by every entity that needs this metadata.
import java.util.Date; import javax.persistence.EntityListeners; import javax.persistence.MappedSuperclass; import javax.persistence.Temporal; import javax.persistence.TemporalType; import org.springframework.data.annotation.CreatedBy; import org.springframework.data.annotation.CreatedDate; import org.springframework.data.annotation.LastModifiedBy; import org.springframework.data.annotation.LastModifiedDate; import org.springframework.data.jpa.domain.support.AuditingEntityListener; /** * * @author javadevcorner.com */ @MappedSuperclass @EntityListeners(AuditingEntityListener.class) public abstract class Auditable<T> { @CreatedBy protected T createdBy; @CreatedDate @Temporal(TemporalType.TIMESTAMP) protected Date creationDate; @LastModifiedBy private T lastModifiedBy; @LastModifiedDate @Temporal(TemporalType.TIMESTAMP) protected Date lastModifiedDate; //setter and getter }
Because this class will be Superclass to many other entities please annotate it with @MappedSuperclass.
Because we want to store auditing information we use the AuditingEntityListener to capture this information on persisting or updating entries. The AuditingEntityListener works via Callback Methods annotated with the @PrePersist and @PreUpdate annotation.
Modify Hero
As you maybe already mentioned the Auditable Superclass has to become the mother of our hero.
@Entity public class Hero extends Auditable<String> { //everything else stays the same - no changes needed }
If you would start the application now and would retrieve some data, every thing (except the hero specifiy attributes) would be null. So there´s something missing.
Enable and Define JPA Auditing
The thing that is missing for our example is the configuration of our JPA.
import java.util.Optional; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.data.domain.AuditorAware; import org.springframework.data.jpa.repository.config.EnableJpaAuditing; /** * * @author javadevcorner.com */ @Configuration @EnableJpaAuditing public class AuditingConfiguration { @Bean public AuditorAware<String> auditorProvider() { return new AuditorAwareImpl(); } class AuditorAwareImpl implements AuditorAware<String> { @Override public Optional<String> getCurrentAuditor() { return Optional.of("javadevcorner"); //If you´re working with the security context here is the place //where you call your context and get the user } } }
We use this class to define which bean should be used for the awaring process (auditorProvider()).
I´ve made an inner class for the implementation of the AuditorAware. But feel free to change it in an external class.
Conclusion
We looked at the problematic of storing Metadata within the persisting and updating process of (new) entries to our database. After we have added the correct maven dependencies we wrote a Superclass with the metadata as attributes and placed it in the inheritance tree above our hero entity. To perform the actions we wanted to have, we wrote a configuration class for JPA and our implementation of the AuditorAware interface to store who modified the entry.
Thanks for reading and have fun using the Spring Boot way of storing Metadata.
Leave a Reply