Hibernate Interceptors Example Tutorial

This tutorial will guide you through setting up and demonstrating the use of interceptors in Hibernate 6.4 with Java 21. We'll create a simple application that uses interceptors to perform actions before or after certain Hibernate events.

Introduction

Interceptors in Hibernate provide a way to intercept and respond to various Hibernate events, such as entity lifecycle events (e.g., save, update, delete). Interceptors can be used to implement cross-cutting concerns such as logging, auditing, and validation.

In this tutorial, we will:

  1. Set up a Maven project with Hibernate and an H2 database dependency.
  2. Configure Hibernate.
  3. Create an entity class (Product).
  4. Implement an interceptor.
  5. Demonstrate the interceptor with a sample application.

Step 1: Set Up Your Project

1.1 Create a Maven Project

Open your IDE and create a new Maven project.

1.2 Add Dependencies

Update your pom.xml file to include the necessary dependencies for Hibernate and H2 (an in-memory database for simplicity).

<project xmlns="http://maven.apache.org/POM/4.0.0"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.example</groupId>
    <artifactId>hibernate-interceptor-example</artifactId>
    <version>1.0-SNAPSHOT</version>

    <dependencies>
        <!-- Hibernate ORM -->
        <dependency>
            <groupId>org.hibernate.orm</groupId>
            <artifactId>hibernate-core</artifactId>
            <version>6.4.0.Final</version>
        </dependency>

        <!-- H2 Database -->
        <dependency>
            <groupId>com.h2database</groupId>
            <artifactId>h2</artifactId>
            <version>2.1.214</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.10.1</version>
                <configuration>
                    <source>21</source>
                    <target>21</target>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

1.3 Configure Hibernate

Create a file named hibernate.cfg.xml in the src/main/resources directory to configure Hibernate. This file contains the database connection settings and Hibernate properties.

<!DOCTYPE hibernate-configuration PUBLIC
    "-//Hibernate/Hibernate Configuration DTD 3.0//EN"
    "http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">

<hibernate-configuration>
    <session-factory>
        <property name="hibernate.dialect">org.hibernate.dialect.H2Dialect</property>
        <property name="hibernate.connection.driver_class">org.h2.Driver</property>
        <property name="hibernate.connection.url">jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1</property>
        <property name="hibernate.connection.username">sa</property>
        <property name="hibernate.connection.password"></property>
        <property name="hibernate.hbm2ddl.auto">update</property>
        <property name="hibernate.show_sql">true</property>
    </session-factory>
</hibernate-configuration>

Explanation:

  • hibernate.dialect specifies the SQL dialect to be used.
  • hibernate.connection.driver_class specifies the JDBC driver class.
  • hibernate.connection.url specifies the JDBC URL for the database connection.
  • hibernate.connection.username and hibernate.connection.password specify the database credentials.
  • hibernate.hbm2ddl.auto specifies the schema generation strategy.
  • hibernate.show_sql specifies whether to show SQL statements in the logs.

Step 2: Create the Entity Class

Create an entity class Product that will be mapped to a table in the database. This class uses annotations to define the entity and its fields.

package com.example.entity;

import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;

@Entity
public class Product {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;
    private String description;
    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 String getDescription() {
        return description;
    }

    public void setDescription(String description) {
        this.description = description;
    }

    public double getPrice() {
        return price;
    }

    public void setPrice(double price) {
        this.price = price;
    }
}

Explanation:

  • The @Entity annotation specifies that the class is an entity and is mapped to a database table.
  • The @Id annotation specifies the primary key of the entity.
  • The @GeneratedValue(strategy = GenerationType.IDENTITY) annotation specifies that the primary key is auto-incremented.

Step 3: Create the Hibernate Utility Class

Create a utility class HibernateUtil to manage the Hibernate SessionFactory. This class ensures a single instance of SessionFactory is created and provides a method to close it.

package com.example.util;

import org.hibernate.SessionFactory;
import org.hibernate.cfg.Configuration;

public class HibernateUtil {
    private static final SessionFactory sessionFactory = buildSessionFactory();

    private static SessionFactory buildSessionFactory() {
        try {
            // Create the SessionFactory from hibernate.cfg.xml
            return new Configuration().configure().buildSessionFactory();
        } catch (Throwable ex) {
            // Make sure you log the exception, as it might be swallowed
            System.err.println("Initial SessionFactory creation failed." + ex);
            throw new ExceptionInInitializerError(ex);
        }
    }

    public static SessionFactory getSessionFactory() {
        return sessionFactory;
    }

    public static void shutdown() {
        // Close caches and connection pools
        getSessionFactory().close();
    }
}

Explanation:

  • The buildSessionFactory method creates the SessionFactory from the hibernate.cfg.xml configuration file.
  • The getSessionFactory method returns the singleton instance of SessionFactory.
  • The shutdown method closes the SessionFactory to release resources.

Step 4: Implement an Interceptor

Create an interceptor class that implements the EmptyInterceptor interface provided by Hibernate. This class will override methods to intercept various Hibernate events.

package com.example.interceptor;

import org.hibernate.EmptyInterceptor;
import org.hibernate.type.Type;

import java.io.Serializable;

public class ProductInterceptor extends EmptyInterceptor {
    @Override
    public boolean onSave(Object entity, Serializable id, Object[] state, String[] propertyNames, Type[] types) {
        if (entity instanceof Product) {
            System.out.println("Saving product: " + entity);
        }
        return super.onSave(entity, id, state, propertyNames, types);
    }

    @Override
    public boolean onFlushDirty(Object entity, Serializable id, Object[] currentState, Object[] previousState, String[] propertyNames, Type[] types) {
        if (entity instanceof Product) {
            System.out.println("Updating product: " + entity);
        }
        return super.onFlushDirty(entity, id, currentState, previousState, propertyNames, types);
    }

    @Override
    public void onDelete(Object entity, Serializable id, Object[] state, String[] propertyNames, Type[] types) {
        if (entity instanceof Product) {
            System.out.println("Deleting product: " + entity);
        }
        super.onDelete(entity, id, state, propertyNames, types);
    }
}

Explanation:

  • The onSave method is called before an entity is saved.
  • The onFlushDirty method is called before an entity is updated.
  • The onDelete method is called before an entity is deleted.
  • In each method, a message is printed to indicate the operation being performed on the Product entity.

Step 5: Demonstrate the Interceptor

Create a MainApp class to demonstrate the interceptor functionality. This class configures Hibernate to use the interceptor and performs database operations on the Product entity.

package com.example.main;

import com.example.entity.Product;
import com.example.interceptor.ProductInterceptor;
import com.example.util.HibernateUtil;
import org.hibernate.Session;
import org.hibernate.Transaction;

public class MainApp {
    public static void main(String[] args) {
        // Configure Hibernate with the interceptor
        Session session = HibernateUtil.getSessionFactory().withOptions().interceptor(new ProductInterceptor()).openSession();
        Transaction transaction = null;

        try {
            transaction = session.beginTransaction();

            // Create and save a product
            Product product = new Product();
            product.setName("Laptop");
            product.setDescription("A high-performance laptop");
            product.setPrice(1500.00);
            session.save(product);

            // Update the product
            product.setPrice(1400.00);
            session.update(product);

            // Delete the product
            session.delete(product);

            transaction.commit();
        } catch (Exception e) {
            if (transaction != null) {
                transaction.rollback();
            }
            e.printStackTrace();
        } finally {
            session.close();
            HibernateUtil.shutdown();
        }
    }
}

Explanation:

  1. Configure Hibernate with the Interceptor:

    Session session = HibernateUtil.getSessionFactory().withOptions().interceptor(new ProductInterceptor()).openSession();
    

    The withOptions() method is used to configure the Session with the ProductInterceptor.

  2. Begin a Transaction:

    Transaction transaction = session.beginTransaction();
    

    A transaction is started to perform database operations.

  3. Create and Save a Product:

    Product product = new Product();
    product.setName("Laptop");
    product.setDescription("A high-performance laptop");
    product.setPrice(1500.00);
    session.save(product);
    

    A new Product entity is created and saved. The onSave method of the interceptor is called.

  4. Update the Product:

    product.setPrice(1400.00);
    session.update(product);
    

    The Product entity is updated. The onFlushDirty method of the interceptor is called.

  5. Delete the Product:

    session.delete(product);
    

    The Product entity is deleted. The onDelete method of the interceptor is called.

  6. Commit the Transaction:

    transaction.commit();
    

    The transaction is committed to save the changes in the database.

  7. Handle Exceptions and Close the Session:

    if (transaction != null) {
        transaction.rollback();
    }
    session.close();
    HibernateUtil.shutdown();
    

    If an exception occurs, the transaction is rolled back. The session and SessionFactory are closed to release resources.

Sample Output

When you run the MainApp class, you should see the following output:

Saving product: Product{id=null, name='Laptop', description='A high-performance laptop', price=1500.0}
Updating product: Product{id=1, name='Laptop', description='A high-performance laptop', price=1400.0}
Deleting product: Product{id=1, name='Laptop', description='A high-performance laptop', price=1400.0}

This output indicates that the interceptor methods were called successfully during save, update, and delete operations.

Conclusion

In this tutorial, we have successfully demonstrated how to use Hibernate interceptors to intercept and respond to various Hibernate events. We set up a Hibernate project, created an entity class, implemented an interceptor, and demonstrated its functionality with a sample application. This guide provides a solid foundation for implementing cross-cutting concerns such as logging, auditing, and validation in your Hibernate-based applications.

Comments