Improving Performance with Query Caching in Spring Data JPA


Query caching in Spring Data JPA helps improve performance by reducing database access for frequently executed queries. By caching query results, subsequent requests for the same query fetch data directly from the cache instead of executing a database query.

This guide demonstrates how to implement query caching using Hibernate's second-level cache with Spring Data JPA and Ehcache.


Step 1: Set Up the Project

Add Maven Dependencies
Include the following dependencies in your pom.xml file:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
    <groupId>com.h2database</groupId>
    <artifactId>h2</artifactId>
    <scope>runtime</scope>
</dependency>
<dependency>
    <groupId>org.ehcache</groupId>
    <artifactId>ehcache</artifactId>
</dependency>
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <scope>provided</scope>
</dependency>

Here, we use Ehcache as the caching provider and Lombok to reduce boilerplate code.


Configure Application Properties
Add the following properties to application.properties:

# Enable Hibernate's second-level cache
spring.jpa.properties.hibernate.cache.use_second_level_cache=true
spring.jpa.properties.hibernate.cache.region.factory_class=org.hibernate.cache.jcache.JCacheRegionFactory

# Use Ehcache as the caching provider
spring.cache.jcache.config=classpath:ehcache.xml

# Hibernate settings
spring.jpa.hibernate.ddl-auto=update
spring.datasource.url=jdbc:h2:mem:testdb
spring.datasource.driverClassName=org.h2.Driver
spring.datasource.username=sa
spring.datasource.password=password

# Enable SQL logging for verification
spring.jpa.properties.hibernate.show_sql=true
spring.jpa.properties.hibernate.format_sql=true

Step 2: Define the Cache Configuration

Create an ehcache.xml file in the src/main/resources directory:

<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="employeeCache">
        <key-type>java.lang.Long</key-type>
        <value-type>java.lang.Object</value-type>
        <expiry>
            <ttl unit="seconds">300</ttl> <!-- Cache expires in 300 seconds -->
        </expiry>
        <heap unit="entries">100</heap> <!-- Maximum 100 entries in memory -->
    </cache>

</config>

This defines a cache named employeeCache with a TTL of 5 minutes.


Step 3: Create the Entity Class

Define the Employee entity:

import jakarta.persistence.*;
import lombok.*;

@Entity
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@ToString
@Cacheable
public class Employee {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;
    private String department;
    private Double salary;
}

The @Cacheable annotation enables second-level caching for this entity.


Step 4: Define the Repository

Create a repository for the Employee entity:

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.stereotype.Repository;

import java.util.List;

@Repository
public interface EmployeeRepository extends JpaRepository<Employee, Long> {

    @Query("SELECT e FROM Employee e WHERE e.department = ?1")
    List<Employee> findByDepartment(String department);
}

Step 5: Implement the Service Layer

Add a service class to interact with the repository:

import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;

@Service
@Transactional(readOnly = true)
public class EmployeeService {

    private final EmployeeRepository employeeRepository;

    public EmployeeService(EmployeeRepository employeeRepository) {
        this.employeeRepository = employeeRepository;
    }

    public List<Employee> getEmployeesByDepartment(String department) {
        return employeeRepository.findByDepartment(department);
    }
}

Step 6: Create a REST Controller

Expose an API to fetch employees by department:

import org.springframework.web.bind.annotation.*;

import java.util.List;

@RestController
@RequestMapping("/employees")
public class EmployeeController {

    private final EmployeeService employeeService;

    public EmployeeController(EmployeeService employeeService) {
        this.employeeService = employeeService;
    }

    @GetMapping("/by-department")
    public List<Employee> getEmployeesByDepartment(@RequestParam String department) {
        return employeeService.getEmployeesByDepartment(department);
    }
}

Step 7: Initialize Sample Data

Add a DataInitializer class to prepopulate the database:

import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;

import java.util.List;

@Component
public class DataInitializer implements CommandLineRunner {

    private final EmployeeRepository employeeRepository;

    public DataInitializer(EmployeeRepository employeeRepository) {
        this.employeeRepository = employeeRepository;
    }

    @Override
    public void run(String... args) {
        employeeRepository.saveAll(List.of(
                new Employee(null, "Alice", "IT", 60000.0),
                new Employee(null, "Bob", "HR", 50000.0),
                new Employee(null, "Charlie", "IT", 70000.0)
        ));
    }
}

Step 8: Test the Application

Start the Application
Run the Spring Boot application.

Fetch Data via API
Use Postman or an HTTP client to fetch employees by department:

  • GET: http://localhost:8080/employees/by-department?department=IT

On the first request, a database query will execute. Subsequent requests will fetch data from the cache, improving performance.


Verification: SQL Query Logging

Enable SQL logging to confirm caching behavior. Add these properties:

spring.jpa.properties.hibernate.show_sql=true
spring.jpa.properties.hibernate.format_sql=true

On the first request, a SQL query will execute. On subsequent requests, no SQL query will appear, confirming the cache is being utilized.


Query caching in Spring Data JPA significantly enhances application performance by reducing database hits for frequently accessed queries. By integrating Hibernate's second-level cache with Ehcache, you can optimize your application's efficiency with minimal effort.

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