Hibernate Caching Example

This tutorial will guide you through setting up and demonstrating caching in Hibernate 6.4 with Java 21. We'll create a simple application that performs database operations and demonstrates the use of first-level and second-level caching to improve performance.

Introduction

Caching in Hibernate can significantly improve the performance of your application by reducing the number of database queries. Hibernate supports two levels of caching:

  1. First-Level Cache: Also known as the session cache, it is associated with the Session object and is enabled by default.
  2. Second-Level Cache: It is associated with the SessionFactory object and must be explicitly configured.

In this tutorial, we will:

  1. Set up a Maven project with Hibernate and an H2 database dependency.
  2. Configure Hibernate and enable second-level caching.
  3. Create an entity class (User).
  4. Implement methods to demonstrate first-level and second-level caching.
  5. Demonstrate caching 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, H2 (an in-memory database for simplicity), and EHCache (for second-level caching).

<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-caching-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>

        <!-- EHCache -->
        <dependency>
            <groupId>org.ehcache</groupId>
            <artifactId>ehcache</artifactId>
            <version>3.9.6</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 and Enable Second-Level Caching

Create a file named hibernate.cfg.xml in the src/main/resources directory to configure Hibernate and enable second-level caching.

<!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>

        <!-- Second-level cache configuration -->
        <property name="hibernate.cache.use_second_level_cache">true</property>
        <property name="hibernate.cache.region.factory_class">org.hibernate.cache.jcache.JCacheRegionFactory</property>
        <property name="hibernate.javax.cache.provider">org.ehcache.jsr107.EhcacheCachingProvider</property>
        <property name="hibernate.cache.use_query_cache">true</property>
    </session-factory>
</hibernate-configuration>

Create ehcache.xml Configuration File

Create a file named ehcache.xml in the src/main/resources directory to configure EHCache.

<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns="http://www.ehcache.org/v3"
    xsi:schemaLocation="http://www.ehcache.org/v3 http://www.ehcache.org/schema/ehcache-core.xsd">
    <cache alias="default">
        <heap unit="entries">1000</heap>
        <expiry>
            <ttl unit="minutes">10</ttl>
        </expiry>
    </cache>
    <cache alias="com.example.entity.User">
        <heap unit="entries">100</heap>
        <expiry>
            <ttl unit="minutes">10</ttl>
        </expiry>
    </cache>
</config>

Step 2: Create the Entity Class

Create an entity class User that will be mapped to a table in the database. This class uses annotations to define the entity and its fields, including cache configuration.

package com.example.entity;

import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import org.hibernate.annotations.Cache;
import org.hibernate.annotations.CacheConcurrencyStrategy;

@Entity
@Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;
    private String email;

    // 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 getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }
}

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.
  • The @Cache annotation enables second-level caching for the User entity.

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 Caching

Create a class UserService to handle database operations with caching. This class includes methods to demonstrate first-level and second-level caching.

First-Level Cache Demonstration

package com.example.service;

import com.example.entity.User;
import com.example.util.HibernateUtil;
import org.hibernate.Session;
import org.hibernate.Transaction;

public class UserService {

    public void demonstrateFirstLevelCache() {
        Session session = HibernateUtil.getSessionFactory().openSession();
        Transaction transaction = null;

        try {
            transaction = session.beginTransaction();

            // Load user for the first time
            User user1 = session.get(User.class, 1L);
            System.out.println("First query: " + user1.getName());

            // Load user again within the same session
            User user2 = session.get(User.class, 1L);
            System.out.println("Second query: " + user2.getName());

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

    // Methods for second-level cache demonstration can be added here.
}

Second-Level Cache Demonstration

package com.example.service;

import com.example.entity.User;
import com.example.util.HibernateUtil;
import org.hibernate.Session;
import org.hibernate.Transaction;

public class UserService {



    // First-level cache demonstration method

    public void demonstrateSecondLevelCache() {
        Session session1 = HibernateUtil.getSessionFactory().openSession();
        Transaction transaction1 = null;

        try {
            transaction1 = session1.beginTransaction();

            // Load user for the first time
            User user1 = session1.get(User.class, 1L);
            System.out.println("First session, first query: " + user1.getName());

            transaction1.commit();
        } catch (Exception e) {
            if (transaction1 != null) {
                transaction1.rollback();
            }
            e.printStackTrace();
        } finally {
            session1.close();
        }

        // Open a new session and load the same user
        Session session2 = HibernateUtil.getSessionFactory().openSession();
        Transaction transaction2 = null;

        try {
            transaction2 = session2.beginTransaction();

            // Load user again in a new session
            User user2 = session2.get(User.class, 1L);
            System.out.println("Second session, first query: " + user2.getName());

            transaction2.commit();
        } catch (Exception e) {
            if (transaction2 != null) {
                transaction2.rollback();
            }
            e.printStackTrace();
        } finally {
            session2.close();
        }
    }
}

Explanation:

  • The demonstrateFirstLevelCache method loads a user twice within the same session. The second query will not hit the database because the user is cached in the first-level cache.
  • The demonstrateSecondLevelCache method loads a user in two different sessions. The second query will hit the second-level cache, avoiding a database hit.

Step 5: Demonstrate Caching

Create a MainApp class to demonstrate the caching functionality. This class calls the caching demonstration methods of UserService.

package com.example.main;

import com.example.service.UserService;

public class MainApp {
    public static void main(String[] args) {
        UserService userService = new UserService();

        // Add a user to the database (for demonstration purposes)
        userService.createUser("Ramesh Fadatare", "ramesh.fadatare@example.com");

        // Demonstrate first-level cache
        System.out.println("Demonstrating First-Level Cache:");
        userService.demonstrateFirstLevelCache();

        // Demonstrate second-level cache
        System.out.println("Demonstrating Second-Level Cache:");
        userService.demonstrateSecondLevelCache();
    }
}

Explanation:

  1. Create a UserService Instance:

    UserService userService = new UserService();
    

    An instance of UserService is created to call its methods for performing database operations.

  2. Add a User to the Database:

    userService.createUser("Ramesh Fadatare", "ramesh.fadatare@example.com");
    

    A user is added to the database for demonstration purposes.

  3. Demonstrate First-Level Cache:

    System.out.println("Demonstrating First-Level Cache:");
    userService.demonstrateFirstLevelCache();
    

    The demonstrateFirstLevelCache method is called to demonstrate the first-level cache functionality.

  4. Demonstrate Second-Level Cache:

    System.out.println("Demonstrating Second-Level Cache:");
    userService.demonstrateSecondLevelCache();
    

    The demonstrateSecondLevelCache method is called to demonstrate the second-level cache functionality.

Sample Output

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

Demonstrating First-Level Cache:
First query: Ramesh Fadatare
Second query: Ramesh Fadatare
Demonstrating Second-Level Cache:
First session, first query: Ramesh Fadatare
Second session, first query: Ramesh Fadatare

This output indicates that the first-level cache was used to avoid a second database hit within the same session, and the second-level cache was used to avoid a second database hit across different sessions.

Conclusion

In this tutorial, we have successfully demonstrated how to implement caching in Hibernate. We set up a Hibernate project, created an entity class, and implemented caching functionality using first-level and second-level caches. This guide provides a solid foundation for improving the performance of your Hibernate-based applications through caching.

Comments