Lesson 21 / 25

Second-Level and Query Caching

Decide when to use Hibernate's second-level cache and query cache.

Caching beyond one transaction

The first-level cache (the persistence context) lives for one transaction. Hibernate's optional second-level (L2) cache is shared across sessions in the application, caching entity state by ID through a provider such as Ehcache, Infinispan or Caffeine via JCache. Enable it per entity with @Cacheable (and Hibernate's @Cache to set a concurrency strategy: READ_ONLY for immutable reference data, NONSTRICT_READ_WRITE or READ_WRITE for data that changes, TRANSACTIONAL with JTA). The L2 cache helps most for read-mostly reference data loaded by ID: countries, currencies, product categories, configuration. The query cache stores the IDs returned by specific queries and is invalidated whenever any table involved changes, so it suits rarely changing data only. Caveats: in a cluster each node has its own cache unless the provider is distributed or invalidation is replicated; bulk updates and changes made outside Hibernate are not seen; and caching adds memory use and complexity. For most application-level caching, a dedicated cache (for example Redis with explicit keys) is easier to reason about.

Caching read-only reference data

Categories rarely change, so they are cached as read-only.

# application.yml (Hibernate 6+ with JCache and Caffeine or Ehcache on the classpath)
spring:
  jpa:
    properties:
      hibernate:
        cache:
          use_second_level_cache: true
          use_query_cache: false            # enable only for specific, rarely changing queries
          region.factory_class: jcache
        javax.cache.provider: com.github.benmanes.caffeine.jcache.spi.CaffeineCachingProvider

# entity
# @Entity
# @Cacheable
# @org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.READ_ONLY)
# public class Category {
#     @Id private Long id;
#     private String slug;
#     private String name;
# }

Cache by evidence, not by default

Turn on the L2 cache only after measurements show repeated loads of the same rows by ID. Caching volatile, frequently updated entities often makes things slower and introduces stale-data bugs.

Quick check: Which data is the best candidate for Hibernate's second-level cache?

  • Order lines that change every second
  • Data updated by external batch jobs directly in SQL
  • Read-mostly reference data such as countries and categories
  • Every entity by default
Answer

Read-mostly reference data such as countries and categories — L2 caching pays off for rarely changing data that is read often by ID.