Spring Boot – JPA Metadata handling (created…, lastModified…)

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)

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. 

Feel free to share

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.