Skip to content

harman-04/hibernate-l2-query-cache-caffeine

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Hibernate Caching & Eviction (L2 + Query Cache)

Project Overview

Caching is a critical performance optimization for data-heavy applications. This project implements a multi-layered caching strategy using Hibernate and the high-performance Caffeine caching library via the JCache (JSR-107) standard.


Technical Concepts

1. Second-Level (L2) Cache

The L2 Cache is shared across all sessions in a SessionFactory.

  • Scope: Unlike the L1 cache (which is private to one transaction/session), L2 cache persists data globally for the application.
  • Implementation: Enabled in Employee with @Cache(usage = CacheConcurrencyStrategy.READ_WRITE).
  • Behavior: When you fetch an employee by ID, Hibernate checks the L2 cache before hitting the database.

2. Query Cache

While the L2 cache stores individual entities by ID, the Query Cache stores the results of specific JPQL/HQL queries (effectively a list of IDs).

  • Setup: In EmployeeRepository, we use @QueryHints to mark specific methods as cacheable.
  • Workflow:
    1. Hibernate checks the Query Cache for the list of IDs matching the department.
    2. If found, it fetches the actual data for those IDs from the L2 cache.

3. Cache Eviction

Eviction is the process of manually or automatically removing stale data from the cache.

  • The "Force Clear": Our EmployeeService uses emf.getCache().evict(Employee.class, id) to remove a specific employee from the L2 cache, forcing the next request to hit the database.

4. Monitoring with Hibernate Statistics

The CacheConfig bean enables Hibernate's internal performance tracking. Through the /api/cache-stats endpoint, you can see:

  • Cache Hits: How many times data was successfully served from memory.
  • Cache Misses: How many times Hibernate was forced to go to the database.

Component Reference

model/Employee.java & model/Skill.java

  • Both use @Cacheable.
  • Employee uses READ_WRITE because employee data often changes.
  • Skill uses READ_ONLY for better performance on static reference data.

config/CacheConfig.java

Unwraps the JPA EntityManagerFactory to access the native Hibernate Statistics API, making performance data available to the REST controller.

repository/EmployeeRepository.java

Demonstrates using @QueryHints to enable the query cache for complex search methods.


Execution & Testing

  1. Initial Load: The DataLoader inserts "GeekA" and several skills.
  2. Test Cache Hit:
    • GET /api/employee/1 (Initial call: Database hit, L2 Cache population).
    • GET /api/employee/1 (Subsequent call: Served from L2 Cache).
  3. Test Query Cache:
    • GET /api/search/IT (Check Query Cache performance).
  4. Test Eviction:
    • POST /api/evict/1 (Removes GeekA from cache).
    • GET /api/employee/1 (Will result in a database hit again).
  5. Check Stats:
    • GET /api/cache-stats to verify the hit/miss ratio.

Configuration Highlight

The application.properties connects Hibernate to Caffeine using the JCacheRegionFactory. This is a modern, high-speed alternative to the older Ehcache or Infinispan providers.


Summary of All Concepts Used

Concept Educational Purpose Logic
First-Level Cache Transactional consistency. Objects are stored for the life of the current Session.
Second-Level Cache Application performance. Objects are shared across all users in Caffeine RAM.
Query Cache Search speed. Stores list of IDs from common search criteria.
Lazy Loading Memory efficiency. Doesn't load "Skills" until you actually try to use them.
Eager Loading Reduced database round-trips. Fetches "Skills" in the same SQL as the "Employee".
Eviction Data freshness. Uses algorithms like LRU (Least Recently Used) to keep only the best data in RAM.

Output as console

Hibernate:
    create table employee (
        id bigint not null auto_increment,
        department varchar(255),
        name varchar(255),
        primary key (id)
    ) engine=InnoDB
Hibernate: 
    create table employee_skills (
        employees_id bigint not null,
        skills_id bigint not null,
        primary key (employees_id, skills_id)
    ) engine=InnoDB
Hibernate: 
    create table skills (
        id bigint not null auto_increment,
        name varchar(255),
        primary key (id)
    ) engine=InnoDB
Hibernate: 
    alter table employee_skills 
       add constraint FK8gyvp36eysxc5o4taxnjahali 
       foreign key (skills_id) 
       references skills (id)
Hibernate: 
    alter table employee_skills 
       add constraint FKg2w4kdn6q5qe3dnuwssg6akn1 
       foreign key (employees_id) 
       references employee (id)
2026-01-17T19:00:16.410+05:30  INFO 13768 --- [Hibernate-Cache-And-Eviction] [           main] j.LocalContainerEntityManagerFactoryBean : Initialized JPA EntityManagerFactory for persistence unit 'default'
2026-01-17T19:00:16.512+05:30  INFO 13768 --- [Hibernate-Cache-And-Eviction] [           main] o.s.d.j.r.query.QueryEnhancerFactories   : Hibernate is in classpath; If applicable, HQL parser will be used.
2026-01-17T19:00:16.798+05:30  WARN 13768 --- [Hibernate-Cache-And-Eviction] [           main] JpaBaseConfiguration$JpaWebConfiguration : spring.jpa.open-in-view is enabled by default. Therefore, database queries may be performed during view rendering. Explicitly configure spring.jpa.open-in-view to disable this warning
2026-01-17T19:00:17.213+05:30  INFO 13768 --- [Hibernate-Cache-And-Eviction] [           main] o.s.boot.tomcat.TomcatWebServer          : Tomcat started on port 8080 (http) with context path '/'
2026-01-17T19:00:17.220+05:30  INFO 13768 --- [Hibernate-Cache-And-Eviction] [           main] A.E.HibernateCacheAndEvictionApplication : Started HibernateCacheAndEvictionApplication in 11.511 seconds (process running for 12.265)
Hibernate: 
    insert 
    into
        skills
        (name) 
    values
        (?)
Hibernate: 
    insert 
    into
        skills
        (name) 
    values
        (?)
Hibernate: 
    insert 
    into
        employee
        (department, name) 
    values
        (?, ?)
--- Initial Data Loader ---
Hibernate: 
    insert 
    into
        employee_skills
        (employees_id, skills_id) 
    values
        (?, ?)
Hibernate: 
    insert 
    into
        employee_skills
        (employees_id, skills_id) 
    values
        (?, ?)
2026-01-17T19:05:32.640+05:30  INFO 13768 --- [Hibernate-Cache-And-Eviction] [nio-8080-exec-6] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring DispatcherServlet 'dispatcherServlet'
2026-01-17T19:05:32.734+05:30  INFO 13768 --- [Hibernate-Cache-And-Eviction] [nio-8080-exec-6] o.s.web.servlet.DispatcherServlet        : Initializing Servlet 'dispatcherServlet'
2026-01-17T19:05:33.364+05:30  INFO 13768 --- [Hibernate-Cache-And-Eviction] [nio-8080-exec-6] o.s.web.servlet.DispatcherServlet        : Completed initialization in 622 ms
Hibernate: 
    select
        e1_0.id,
        e1_0.department,
        e1_0.name 
    from
        employee e1_0 
    where
        e1_0.id=?
Hibernate: 
    select
        s1_0.employees_id,
        s1_1.id,
        s1_1.name 
    from
        employee_skills s1_0 
    join
        skills s1_1 
            on s1_1.id=s1_0.skills_id 
    where
        s1_0.employees_id=?
Hibernate: 
    select
        e1_0.skills_id,
        e1_1.id,
        e1_1.department,
        e1_1.name 
    from
        employee_skills e1_0 
    join
        employee e1_1 
            on e1_1.id=e1_0.employees_id 
    where
        e1_0.skills_id=?
Hibernate: 
    select
        s1_0.employees_id,
        s1_1.id,
        s1_1.name 
    from
        employee_skills s1_0 
    join
        skills s1_1 
            on s1_1.id=s1_0.skills_id 
    where
        s1_0.employees_id=?
2026-01-17T19:05:41.561+05:30  WARN 13768 --- [Hibernate-Cache-And-Eviction] [nio-8080-exec-6] .w.s.m.s.DefaultHandlerExceptionResolver : Resolved [org.springframework.http.converter.HttpMessageNotWritableException: Could not write JSON: (was java.util.ConcurrentModificationException)]
Hibernate: 
    select
        e1_0.id,
        e1_0.department,
        e1_0.name 
    from
        employee e1_0 
    where
        e1_0.department=?
Cache Evicted for Employee id: 1  next search will hit database
Hibernate: 
    select
        e1_0.id,
        e1_0.department,
        e1_0.name 
    from
        employee e1_0 
    where
        e1_0.id=?

PostMan Api Response

GET localhost:8080/api/employee/1

{
    "id": 1,
    "name": "GeekA",
    "department": "IT",
    "skills": []
}

GET localhost:8080/api/search/IT

[
    {
        "id": 1,
        "name": "GeekA",
        "department": "IT",
        "skills": []
    }
]

POST localhost:8080/api/evict/1

Cache cleared for id: 1 Check console for next Search.

GET localhost:8080/api/cache-stats

--- Hibernate Cache Stats ---
Entity Fetch Count: 0
Second Level Cache Hits: 8
Second Level Cache Misses: 4
Query Cache Hits: 2
Query Cache Misses: 1

About

A Spring Boot 4.0.1 demo of Hibernate Second-Level and Query caching using Caffeine (JCache). Includes statistics tracking and manual cache eviction logic.

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages