Spring Boot JPA Interceptor Guide: @PrePersist, @PreUpdate & Hibernate API


An Interceptor in Spring Data JPA allows you to modify queries, track changes, log operations, or enforce business rules dynamically.

Steps to Implement Interceptor in Spring Data JPA

  1. Create a Spring Boot Application
  2. Define an Entity
  3. Create a Repository
  4. Implement a JPA Interceptor (Entity Listener or Hibernate Interceptor)
  5. Register the Interceptor in Spring Boot

1. Create a Spring Boot Application

Create a Spring Boot project using Spring Initializr with dependencies:

  • Spring Web
  • Spring Data JPA
  • H2 Database (for testing)

2. Define an Entity

Let's define a simple Product entity.

package com.example.interceptor.model;

import jakarta.persistence.*;

@Entity
@EntityListeners(ProductInterceptor.class) // Attach the interceptor
public class Product {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    private String name;
    
    private Double price;

    // Getters and Setters
    public Long getId() { return id; }
    public void setId(Long id) { this.id = id; }

    public String getName() { return name; }
    public void setName(String name) { this.name = name; }

    public Double getPrice() { return price; }
    public void setPrice(Double price) { this.price = price; }
}

3. Create a Repository

package com.example.interceptor.repository;

import com.example.interceptor.model.Product;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface ProductRepository extends JpaRepository<Product, Long> {
}

4. Implement an Interceptor

There are two approaches to intercept changes:
(A) Using EntityListeners with @PrePersist and @PreUpdate
(B) Using Hibernate’s Interceptor API


(A) Entity Listeners - Simple Approach

This is useful if you want to trigger logic before persisting or updating an entity.

package com.example.interceptor.interceptor;

import com.example.interceptor.model.Product;
import jakarta.persistence.PrePersist;
import jakarta.persistence.PreUpdate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class ProductInterceptor {

    private static final Logger logger = LoggerFactory.getLogger(ProductInterceptor.class);

    @PrePersist
    public void beforeInsert(Product product) {
        logger.info("Before Insert: Setting default price if null...");
        if (product.getPrice() == null) {
            product.setPrice(100.0); // Default price
        }
    }

    @PreUpdate
    public void beforeUpdate(Product product) {
        logger.info("Before Update: Updating product: " + product.getName());
    }
}
  • @PrePersist: Called before inserting a new row.
  • @PreUpdate: Called before updating an existing row.

This approach does not require Hibernate dependencies.


(B) Hibernate Interceptor - Advanced Approach

If you want deeper control, like modifying queries at runtime, log SQL statements, or apply dynamic changes, use a Hibernate Interceptor.

Step 1: Create Custom Hibernate Interceptor

package com.example.interceptor.interceptor;

import org.hibernate.EmptyInterceptor;
import org.hibernate.type.Type;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import java.io.Serializable;

@Component
public class CustomHibernateInterceptor extends EmptyInterceptor {

    private static final Logger logger = LoggerFactory.getLogger(CustomHibernateInterceptor.class);

    @Override
    public boolean onSave(Object entity, Serializable id, Object[] state, String[] propertyNames, Type[] types) {
        logger.info("Entity Saving: {}", entity.getClass().getSimpleName());
        return super.onSave(entity, id, state, propertyNames, types);
    }

    @Override
    public boolean onFlushDirty(Object entity, Serializable id, Object[] currentState, Object[] previousState, 
                                String[] propertyNames, Type[] types) {
        logger.info("Entity Updating: {}", entity.getClass().getSimpleName());
        return super.onFlushDirty(entity, id, currentState, previousState, propertyNames, types);
    }
}
  • onSave: Triggers when an entity is saved.
  • onFlushDirty: Triggers when an entity is updated.

Step 2: Register Interceptor in Hibernate Properties

To register the interceptor, override LocalSessionFactoryBean.

package com.example.interceptor.config;

import com.example.interceptor.interceptor.CustomHibernateInterceptor;
import org.hibernate.SessionFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.orm.jpa.vendor.HibernateJpaSessionFactoryBean;

@Configuration
public class HibernateConfig {

    @Autowired
    private CustomHibernateInterceptor customInterceptor;

    @Bean
    public HibernateJpaSessionFactoryBean sessionFactoryBean() {
        HibernateJpaSessionFactoryBean factoryBean = new HibernateJpaSessionFactoryBean();
        factoryBean.setJpaPropertyMap(Map.of("hibernate.ejb.interceptor", customInterceptor));
        return factoryBean;
    }
}

5. Testing the Interceptor

Create a simple REST controller to test.

package com.example.interceptor.controller;

import com.example.interceptor.model.Product;
import com.example.interceptor.repository.ProductRepository;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@RestController
@RequestMapping("/products")
public class ProductController {

    private final ProductRepository repository;

    public ProductController(ProductRepository repository) {
        this.repository = repository;
    }

    @PostMapping
    public Product addProduct(@RequestBody Product product) {
        return repository.save(product);
    }

    @GetMapping
    public List<Product> getProducts() {
        return repository.findAll();
    }
}

6. Running the Application

  1. Run the Spring Boot application.
  2. Use Postman or cURL to test.

Insert a Product

curl -X POST "http://localhost:8080/products" -H "Content-Type: application/json" -d '{"name": "Laptop", "price": null}'

💡 Interceptor will set price to 100.0 if it's null!

Get All Products

curl -X GET "http://localhost:8080/products"

Conclusion

  • If you need basic lifecycle hooks, use @PrePersist and @PreUpdate.
  • If you need deep control over Hibernate queries, use EmptyInterceptor.

Popular posts from this blog

Learn Java 8 streams with an example - print odd/even numbers from Array and List

Java Stream API - How to convert List of objects to another List of objects using Java streams?

Registration and Login with Spring Boot + Spring Security + Thymeleaf

Java, Spring Boot Mini Project - Library Management System - Download

ReactJS, Spring Boot JWT Authentication Example

Top 5 Java ORM tools - 2024

Java - Blowfish Encryption and decryption Example

Spring boot video streaming example-HTML5

Google Cloud Storage + Spring Boot - File Upload, Download, and Delete