DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Please enter at least three characters to search
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Zones

Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks

Because the DevOps movement has redefined engineering responsibilities, SREs now have to become stewards of observability strategy.

Apache Cassandra combines the benefits of major NoSQL databases to support data management needs not covered by traditional RDBMS vendors.

The software you build is only as secure as the code that powers it. Learn how malicious code creeps into your software supply chain.

Generative AI has transformed nearly every industry. How can you leverage GenAI to improve your productivity and efficiency?

Related

  • Introduction to Spring Data Elasticsearch 4.1
  • Introduction To Spring Data JPA With Inheritance in A REST Application
  • Advanced Search and Filtering API Using Spring Data and MongoDB
  • Manage Hierarchical Data in MongoDB With Spring

Trending

  • Software Delivery at Scale: Centralized Jenkins Pipeline for Optimal Efficiency
  • Traditional Testing and RAGAS: A Hybrid Strategy for Evaluating AI Chatbots
  • Manual Sharding in PostgreSQL: A Step-by-Step Implementation Guide
  • Can You Run a MariaDB Cluster on a $150 Kubernetes Lab? I Gave It a Shot
  1. DZone
  2. Data Engineering
  3. Databases
  4. JPA Auditing: Automatically Persisting Audit Logs Using EntityListeners

JPA Auditing: Automatically Persisting Audit Logs Using EntityListeners

When creating audit logs, consider using Spring Data. Its JPA functionality allows for EntityListeners and callback methods to update the needed properties.

By 
Naresh Joshi user avatar
Naresh Joshi
DZone Core CORE ·
May. 27, 17 · Tutorial
Likes (21)
Comment
Save
Tweet
Share
75.6K Views

Join the DZone community and get the full member experience.

Join For Free

In my previous article, Spring Data JPA Auditing: Saving CreatedBy, CreatedDate, LastModifiedBy, LastModifiedDate automatically, I discussed why auditing is important for any business application and how we can use Spring Data JPA automate it.

I also discussed how Spring Data uses JPA's EntityListeners and callback methods to automatically update the CreatedBy, CreatedDate, LastModifiedBy, and LastModifiedDate properties.

Well, in this article, I am going dig a little bit deeper and discuss how we can use JPA EntityListeners to create audit logs and keep information of every insert, update, and delete operation on our data.

I will take the File entity example from the previous article and walk you through the necessary steps and code portions you will need to include in our project to automate the auditing process.

We will use Spring Boot, Spring Data JPA (because it gives us complete JPA functionality plus some nice customization by Spring), and MySQL to demonstrate this.

We will need to add below parent and dependencies to our POM file:

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>1.5.1.RELEASE</version>
    <relativePath/>
</parent>

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-jpa</artifactId>
    </dependency>

    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <scope>runtime</scope>
    </dependency>
</dependencies>


Implementing JPA Callback Methods Using @PrePersist, @PreUpdate, @PreRemove

JPA provides us the functionality to define callback methods for any entity using the @PrePersist, @PreUpdate, and @PreRemove annotations. And these methods will get invoked before their respective lifecycle event.

Similar to pre-annotations, JPA also provides post annotations like @PostPersist, @PostUpdate, @PostRemove, and @PostLoad. We can use them to define callback methods that will get triggered after the event.

JPA-Automatic-Auditing-Saving-Audit-Logs

The name of the annotation can tell you its respective event. For example:

  • @PrePersist: Before the entity persists.

  • @PostUpdate: After the entity gets updated.

And this is the same for other annotations as well.

Defining Callback Methods Inside Entity

We can define callback methods inside our entity class, but we need to follow some rules. For example, internal callback methods should always return void and take no arguments. They can have any name and any access level and can also be static.

@Entity
public class File {

    @PrePersist
    public void prePersist() { 

    @PreUpdate
    public void preUpdate() { 

    @PreRemove
    public void preRemove() { 

}


Defining Callback Methods in an External Class and Use @EntityListeners

We can also define our callback methods in an external listener class in a manner that they should always return void and accepts a target object as the argument. However, they can have any name and any access level and can also be static.

public class FileEntityListener {
    @PrePersist
    public void prePersist(File target) { 

    @PreUpdate
    public void preUpdate(File target) { 

    @PreRemove
    public void preRemove(File target) { 
}


And we will need to register this FileEntityListener class on File entity or its super class by using the @EntityListeners annotation:

@Entity
@EntityListeners(FileEntityListener.class)
class File extends Auditable<String> {

    @Id
    @GeneratedValue
    private Integer id;
    private String name;
    private String content;


}


Advantages of Using @EntityListeners

  • First of all, we should not write any kind of business logic in our entity classes and follow the Single Responsibility Principle. Every entity class should be a POJO (Plain Old Java Object).
  • We can have only one callback method for a particular event in a single class — e.g. only one callback method with @PrePresist is allowed in a class. Meanwhile, we can define more than one listener class in @EntityListeners, and every listener class can have a @PrePersist.

For example, I have used @EntityListeners on File and provided the FileEntityListener class to it. I I have also extended an auditable class in the File class.

The auditable class itself has an @EntityListeners on it with the AuditingEntityListener class because I am using this class to persist createdBy and the other above-mentioned properties. You can check my previous article, Spring Data JPA Auditing: Saving CreatedBy, CreatedDate, LastModifiedBy, LastModifiedDate automatically for more details.

@MappedSuperclass
@EntityListeners(AuditingEntityListener.class)
public abstract class Auditable<U> {

    @CreatedBy
    protected U createdBy;

    @CreatedDate
    @Temporal(TIMESTAMP)
    protected Date createdDate;

    @LastModifiedBy
    protected U lastModifiedBy;

    @LastModifiedDate
    @Temporal(TIMESTAMP)
    protected Date lastModifiedDate;


}


We will also need to provide getters, setters, constructors, toString, and equals methods to all the entities. However, you may like to check out Project Lombok: The Boilerplate Code Extractor if you want to auto-generate these things.

Now we are all set and we need to implement our logging strategy. We can store history logs of the File in a separate history table: FileHistory.

@Entity
@EntityListeners(AuditingEntityListener.class)
public class FileHistory {

    @Id
    @GeneratedValue
    private Integer id;

    @ManyToOne
    @JoinColumn(name = "file_id", foreignKey = @ForeignKey(name = "FK_file_history_file"))
    private File file;

    private String fileContent;

    @CreatedBy
    private String modifiedBy;

    @CreatedDate
    @Temporal(TIMESTAMP)
    private Date modifiedDate;

    @Enumerated(STRING)
    private Action action;

    public FileHistory() {
    }

    public FileHistory(File file, Action action) {
        this.file = file;
        this.fileContent = file.toString();
        this.action = action;
    }


}


Here, Action is an enum:

public enum Action {

    INSERTED("INSERTED"),
    UPDATED("UPDATED"),
    DELETED("DELETED");

    private final String name;

    private Action(String value) {
        this.name = value;
    }

    public String value() {
        return this.name;
    }

    @Override
    public String toString() {
        return name;
    }
}


And we will need to insert an entry in FileHistory for every insert, update, and delete operation, and we need to write that logic inside our FileEntityListener class. For this purpose, we will need to inject either repository class or EntityManager in FileEntityListener class.

Injecting Spring-Managed Beans Like EntityManager in EntityListeners

But here we have a problem. EntityListeners are instantiated by JPA, not Spring, so Spring cannot inject any Spring-managed bean, e.g. EntityManager in any EntityListeners.

So if you try to auto-wire EntityManager inside the FileEntityListener class, it will not work:

@Autowired EntityManager entityManager; //Will not work and entityManager will be null always


I have also written a separate article on how to AutoWire Spring Beans Into Classes Not Managed By Spring Like JPA Entity Listeners, you can read it if you want to know more.

And I am using the same idea here to make it work. We will create a utility class to fetch Spring managed beans for us:

@Service
public class BeanUtil implements ApplicationContextAware {

    private static ApplicationContext context;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        context = applicationContext;
    }

    public static <T> T getBean(Class<T> beanClass) {
        return context.getBean(beanClass);
    }

}


And now we will write history record creation logic inside FileEntityListener:

public class FileEntityListener {

    @PrePersist
    public void prePersist(File target) {
        perform(target, INSERTED);
    }

    @PreUpdate
    public void preUpdate(File target) {
        perform(target, UPDATED);
    }

    @PreRemove
    public void preRemove(File target) {
        perform(target, DELETED);
    }

    @Transactional(MANDATORY)
    private void perform(File target, Action action) {
        EntityManager entityManager = BeanUtil.getBean(EntityManager.class);
        entityManager.persist(new FileHistory(target, action));
    }

}


And now if we try to persist or update and file objects, these auditing properties will automatically be saved.

You can find complete code on this GitHub Repository and please feel free to provide your valuable feedback.

Database Spring Framework Spring Data

Published at DZone with permission of Naresh Joshi, DZone MVB. See the original article here.

Opinions expressed by DZone contributors are their own.

Related

  • Introduction to Spring Data Elasticsearch 4.1
  • Introduction To Spring Data JPA With Inheritance in A REST Application
  • Advanced Search and Filtering API Using Spring Data and MongoDB
  • Manage Hierarchical Data in MongoDB With Spring

Partner Resources

×

Comments
Oops! Something Went Wrong

The likes didn't load as expected. Please refresh the page and try again.

ABOUT US

  • About DZone
  • Support and feedback
  • Community research
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 100
  • Nashville, TN 37211
  • support@dzone.com

Let's be friends:

Likes
There are no likes...yet! 👀
Be the first to like this post!
It looks like you're not logged in.
Sign in to see who liked this post!