Enabling and Validating Hibernate Second‑Level Cache for Read‑Intensive Applications
Learn how to add a second‑level cache to Hibernate, configure it with Ehcache, annotate entities, and verify cache hits with statistics. A step‑by‑step guide for Java developers.
12 Mar 2026, 23:28 UTC

Desired Outcome
Enable Hibernate’s second‑level cache so that frequently read entities are served from an in‑memory cache instead of hitting the database on every request. Verify that cache hits increase after the first query execution.
Prerequisites
- Java 17+ application using Hibernate 6.x.
- Build tool (Maven or Gradle) to add dependencies.
- Database with at least one read‑heavy entity.
- Administrative access to the JVM to enable JMX if needed.
Step 1 – Add a Cache Provider Dependency
Hibernate supports several cache providers; Ehcache is a common choice. Add the following to your pom.xml (Maven) or build.gradle (Gradle).
<dependency>
<groupId>org.ehcache</groupId>
<artifactId>ehcache</artifactId>
<version>3.10.8</version>
</dependency>
<dependency>
<groupId>org.hibernate.orm</groupId>
<artifactId>hibernate-ehcache</artifactId>
<version>6.2.7.Final</version>
</dependency>
Step 2 – Configure Hibernate Cache Factory
In hibernate.cfg.xml or your application.yml, set the cache region factory and statistics flag.
<hibernate-configuration>
<session-factory>
<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.generate_statistics">true</property>
</session-factory>
</hibernate-configuration>
For Spring Boot, the equivalent application.yml entries are:
hibernate:
cache:
use_second_level_cache: true
region:
factory_class: org.hibernate.cache.jcache.JCacheRegionFactory
generate_statistics: true
Step 3 – Annotate Entities
Mark each entity that should be cached. Use @Cacheable to enable caching and @Cache to set the concurrency strategy.
@Entity
@Cacheable
@Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
public class Product {
@Id
private Long id;
private String name;
// getters/setters
}
Choose READ_ONLY if the entity never changes, READ_WRITE for typical CRUD, or NONE to exclude an entity.
Step 4 – Enable Statistics (Already Done)
With hibernate.generate_statistics=true, Hibernate will expose hit/miss counts via the Statistics API.
Step 5 – Run a Read‑Heavy Test Query Twice
Execute a simple JPQL query in a test or a small standalone Java program. Use the same SessionFactory instance to keep the cache active.
SessionFactory sf = HibernateUtil.getSessionFactory();
Session session = sf.openSession();
try {
// First run – expected to hit the DB
List<Product> first = session.createQuery("from Product", Product.class).list();
// Second run – should hit the cache
List<Product> second = session.createQuery("from Product", Product.class).list();
} finally {
session.close();
}
Step 6 – Verify Cache Hits
After the test, inspect the statistics.
Statistics stats = sf.getStatistics();
System.out.println("Cache Hit Count: " + stats.getCacheHitCount());
System.out.println("Cache Miss Count: " + stats.getCacheMissCount());
Expect Cache Hit Count to be greater than zero after the second query. If it remains zero, the cache is not being used; check the configuration and entity annotations.
Step 7 – Monitor Memory & Eviction
Large cache regions can consume memory. Use Ehcache’s JMX console (or the jcache API) to view region sizes and eviction stats. Example JMX query:
jconsole <java_process_id>
# Navigate to MBeans → org.ehcache → CacheManager →
# Inspect attributes such as Size, Hit Ratio, Miss Ratio
Adjust maxElementsInMemory or timeToIdleSeconds in the Ehcache configuration if necessary.
Step 8 – Recovery / Rollback
If the cache causes stale data or memory issues, disable it by setting hibernate.cache.use_second_level_cache to false and removing @Cacheable annotations. Restart the application to clear any existing cache entries.
Limitations & Practical Checks
- Second‑level cache is read‑through only; write operations still hit the database unless you configure
CacheConcurrencyStrategyappropriately. - Bulk updates, native SQL, or
@Cacheable(false)bypass the cache. Use explicit cache eviction orSessionFactory.getCache().evictAll()after such operations. - Entities with mutable natural keys can produce stale cache entries. Prefer immutable keys or versioned entities.
- Always test in a staging environment before enabling the cache in production to avoid unexpected data consistency problems.
Practical Result Check
After following the steps, run a quick sanity test: execute the same query, then perform an UPDATE on a cached entity, and query again. Verify that the cache was invalidated (miss count increases) or that the updated data is reflected. If not, manually evict the entity: sessionFactory.getCache().evictEntityData(Product.class, productId);.
Conclusion
By adding a cache provider, configuring Hibernate, annotating entities, and validating via statistics, you can significantly reduce read latency for read‑heavy workloads. Remember to monitor memory usage and handle cache invalidation for write operations to maintain data consistency.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.