13. Caching

At runtime, Hibernate handles moving data into and out of the second-level cache in response to the operations performed by the Session, which acts as a transaction-level cache of persistent data. Once an entity becomes managed, that object is added to the internal cache of the current persistence context (EntityManager or Session). The persistence context is also called the first-level cache, and it’s enabled by default.

It is possible to configure a JVM-level (SessionFactory-level) or even a cluster cache on a class-by-class and collection-by-collection basis.

Be aware that Hibernate caches are not aware of changes made to the persistent store by other applications.

To address this limitation, you can configure a TTL (Time To Live) retention policy at the second-level cache region level so that the underlying cache entries expire regularly.

13.1. Configuring second-level caching

Hibernate can integrate with various caching providers for the purpose of caching data outside the context of a particular Session. This section defines the settings which control this behavior.

13.1.1. RegionFactory

org.hibernate.cache.spi.RegionFactory defines the integration between Hibernate and a pluggable caching provider. hibernate.cache.region.factory_class is used to declare the provider to use. Hibernate comes with built-in support for the Java caching standard JCache and also two popular caching libraries: Ehcache and Infinispan. Detailed information is provided later in this chapter.

13.1.2. Caching configuration properties

Besides provider specific configuration, there are a number of configurations options on the Hibernate side of the integration that control various caching behaviors:

hibernate.cache.use_second_level_cache

Enable or disable second level caching overall. By default, if the currently configured RegionFactory is not the NoCachingRegionFactory, then the second-level cache is going to be enabled. Otherwise, the second-level cache is disabled.

hibernate.cache.use_query_cache

Enable or disable second level caching of query results. The default is false.

hibernate.cache.query_cache_factory

Query result caching is handled by a special contract that deals with staleness-based invalidation of the results. The default implementation does not allow stale results at all. Use this for applications that would like to relax that. Names an implementation of org.hibernate.cache.spi.QueryCacheFactory.

hibernate.cache.use_minimal_puts

Optimizes second-level cache operations to minimize writes, at the cost of more frequent reads. Providers typically set this appropriately.

hibernate.cache.region_prefix

Defines a name to be used as a prefix to all second-level cache region names.

hibernate.cache.default_cache_concurrency_strategy

In Hibernate second-level caching, all regions can be configured differently including the concurrency strategy to use when accessing that particular region. This setting allows defining a default strategy to be used. This setting is very rarely required as the pluggable providers do specify the default strategy to use. Valid values include:

  • read-only,

  • read-write,

  • nonstrict-read-write,

  • transactional

hibernate.cache.use_structured_entries

If true, forces Hibernate to store data in the second-level cache in a more human-friendly format. Can be useful if you’d like to be able to “browse” the data directly in your cache, but does have a performance impact.

hibernate.cache.auto_evict_collection_cache

Enables or disables the automatic eviction of a bidirectional association’s collection cache entry when the association is changed just from the owning side. This is disabled by default, as it has a performance impact to track this state. However, if your application does not manage both sides of bidirectional association where the collection side is cached, the alternative is to have stale data in that collection cache.

hibernate.cache.use_reference_entries

Enable direct storage of entity references into the second level cache for read-only or immutable entities.

hibernate.cache.keys_factory

When storing entries into the second-level cache as a key-value pair, the identifiers can be wrapped into tuples to guarantee uniqueness in case that second-level cache stores all entities in single space. These tuples are then used as keys in the cache. When the second-level cache implementation (incl. its configuration) guarantees that different entity types are stored separately and multi-tenancy is not used, you can omit this wrapping to achieve better performance. Currently, this property is only supported when Infinispan is configured as the second-level cache implementation. Valid values are:

  • default (wraps identitifers in the tuple)

  • simple (uses identifiers as keys without any wrapping)

  • fully qualified class name that implements org.hibernate.cache.spi.CacheKeysFactory

13.2. Configuring second-level cache mappings

The cache mappings can be configured via JPA annotations or XML descriptors or using the Hibernate-specific mapping files.

By default, entities are not part of the second level cache and we recommend you to stick to this setting. However, you can override this by setting the shared-cache-mode element in your persistence.xml file or by using the javax.persistence.sharedCache.mode property in your configuration file. The following values are possible:

ENABLE_SELECTIVE (Default and recommended value)

Entities are not cached unless explicitly marked as cacheable (with the @Cacheable annotation).

DISABLE_SELECTIVE

Entities are cached unless explicitly marked as non-cacheable.

ALL

Entities are always cached even if marked as non-cacheable.

NONE

No entity is cached even if marked as cacheable. This option can make sense to disable second-level cache altogether.

The cache concurrency strategy used by default can be set globally via the hibernate.cache.default_cache_concurrency_strategy configuration property. The values for this property are:

read-only

If your application needs to read, but not modify, instances of a persistent class, a read-only cache is the best choice. Application can still delete entities and these changes should be reflected in second-level cache so that the cache does not provide stale entities. Implementations may use performance optimizations based on the immutability of entities.

read-write

If the application needs to update data, a read-write cache might be appropriate. This strategy provides consistent access to single entity, but not a serializable transaction isolation level; e.g. when TX1 reads looks up an entity and does not find it, TX2 inserts the entity into cache and TX1 looks it up again, the new entity can be read in TX1.

nonstrict-read-write

Similar to read-write strategy but there might be occasional stale reads upon concurrent access to an entity. The choice of this strategy might be appropriate if the application rarely updates the same data simultaneously and strict transaction isolation is not required. Implementations may use performance optimizations that make use of the relaxed consistency guarantee.

transactional

Provides serializable transaction isolation level.

Rather than using a global setting, it is recommended to define the cache concurrency strategy on a per entity basis.

Use the @org.hibernate.annotations.Cache annotation for this purpose.

The @Cache annotation define three attributes:

usage

Defines the CacheConcurrencyStrategy

region

Defines a cache region where entries will be stored

include

If lazy properties should be included in the second level cache. The default value is all so lazy properties are cacheable. The other possible value is non-lazy so lazy properties are not cacheable.

13.3. Entity inheritance and second-level cache mapping

Traditionally, when using entity inheritance, Hibernate required an entity hierarchy to be either cached entirely or not cached at all. Therefore, if you wanted to cache a subclass belonging to a given entity hierarchy, the JPA @Cacheable and the Hibernate-specific @Cache annotations would have to be declared at the root-entity level only.

Although we still believe that all entities belonging to a given entity hierarchy should share the same caching semantics, the JPA specification says that the @Cacheable annotation could be overwritten by a subclass:

The value of the Cacheable annotation is inherited by subclasses; it can be overridden by specifying Cacheable on a subclass.

— Section 11.1.7 of the JPA 2.1 Specification

As of Hibernate ORM 5.3, you can now override a base class @Cacheable or @Cache definition at subclass level.

However, the Hibernate cache concurrency strategy (e.g. read-only, nonstrict-read-write, read-write, transactional) is still defined at the root entity level and cannot be overridden.

Nevertheless, the reasons why we advise you to have all entities belonging to an inheritance tree share the same caching definition can be summed as follows:

  • from a performance perspective, adding an additional check on a per entity type level slows the bootstrap process.

  • providing different caching semantics for subclasses would violate the Liskov substitution principle.

13.4. Entity cache

Example 450. Entity cache mapping

  1. @Entity(name = "Phone")
  2. @Cacheable
  3. @org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
  4. public static class Phone {
  5. @Id
  6. @GeneratedValue
  7. private Long id;
  8. private String mobile;
  9. @ManyToOne
  10. private Person person;
  11. @Version
  12. private int version;
  13. //Getters and setters are omitted for brevity
  14. }

Hibernate stores cached entities in a dehydrated form, which is similar to the database representation. Aside from the foreign key column values of the @ManyToOne or @OneToOne child-side associations, entity relationships are not stored in the cache,

Once an entity is stored in the second-level cache, you can avoid a database hit and load the entity from the cache alone:

Example 451. Loading entity using JPA

  1. Person person = entityManager.find( Person.class, 1L );

Example 452. Loading entity using Hibernate native API

  1. Person person = session.get( Person.class, 1L );

The Hibernate second-level cache can also load entities by their natural id:

Example 453. Hibernate natural id entity mapping

  1. @Entity(name = "Person")
  2. @Cacheable
  3. @org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
  4. public static class Person {
  5. @Id
  6. @GeneratedValue(strategy = GenerationType.AUTO)
  7. private Long id;
  8. private String name;
  9. @NaturalId
  10. @Column(name = "code", unique = true)
  11. private String code;
  12. //Getters and setters are omitted for brevity
  13. }

Example 454. Loading entity using Hibernate native natural id API

  1. Person person = session
  2. .byNaturalId( Person.class )
  3. .using( "code", "unique-code")
  4. .load();

13.5. Collection cache

Hibernate can also cache collections, and the @Cache annotation must be on added to the collection property.

If the collection is made of value types (basic or embeddables mapped with @ElementCollection), the collection is stored as such. If the collection contains other entities (@OneToMany or @ManyToMany), the collection cache entry will store the entity identifiers only.

Example 455. Collection cache mapping

  1. @OneToMany(mappedBy = "person", cascade = CascadeType.ALL)
  2. @org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
  3. private List<Phone> phones = new ArrayList<>( );

Collections are read-through, meaning they are cached upon being accessed for the first time:

Example 456. Collection cache usage

  1. Person person = entityManager.find( Person.class, 1L );
  2. person.getPhones().size();

Subsequent collection retrievals will use the cache instead of going to the database.

The collection cache is not write-through so any modification will trigger a collection cache entry invalidation. On a subsequent access, the collection will be loaded from the database and re-cached.

13.6. Query cache

Aside from caching entities and collections, Hibernate offers a query cache too. This is useful for frequently executed queries with fixed parameter values.

Caching of query results introduces some overhead in terms of your applications normal transactional processing. For example, if you cache results of a query against Person, Hibernate will need to keep track of when those results should be invalidated because changes have been committed against any Person entity.

That, coupled with the fact that most applications simply gain no benefit from caching query results, leads Hibernate to disable caching of query results by default.

To use query caching, you will first need to enable it with the following configuration property:

Example 457. Enabling query cache

  1. <property
  2. name="hibernate.cache.use_query_cache"
  3. value="true" />

As mentioned above, most queries do not benefit from caching or their results. So by default, individual queries are not cached even after enabling query caching. Each particular query that needs to be cached must be manually set as cacheable. This way, the query looks for existing cache results or adds the query results to the cache when being executed.

Example 458. Caching query using JPA

  1. List<Person> persons = entityManager.createQuery(
  2. "select p " +
  3. "from Person p " +
  4. "where p.name = :name", Person.class)
  5. .setParameter( "name", "John Doe")
  6. .setHint( "org.hibernate.cacheable", "true")
  7. .getResultList();

Example 459. Caching query using Hibernate native API

  1. List<Person> persons = session.createQuery(
  2. "select p " +
  3. "from Person p " +
  4. "where p.name = :name")
  5. .setParameter( "name", "John Doe")
  6. .setCacheable(true)
  7. .list();

For entity queries, the query cache does not cache the state of the actual entities. Instead, it stores the entity identifiers, and when the query result is fetched from the cache, the entity state is going to be loaded from the second-level cache entity regions.

Just as with collection caching, the query cache should always be used in conjunction with the second-level cache for those entities expected to be cached as part of a query result cache.

For projection queries, the query cache stores the dehydrated entity state (e.g. Object[]) associated with the underlying JDBC ResultSet.

13.6.1. Query cache regions

This setting creates two new cache regions:

default-query-results-region

Holding the cached query results.

default-update-timestamps-region

Holding timestamps of the most recent updates to queryable tables. These are used to validate the results as they are served from the query cache.

If you configure your underlying cache implementation to use expiration, it’s very important that the timeout of the underlying cache region for the default-update-timestamps-region is set to a higher value than the timeout setting of any of the query caches.

In fact, we recommend that the default-update-timestamps-region region is not configured for expiration (time-based) or eviction (size/memory-based) at all. Note that an LRU (Least Recently Used) cache eviction policy is never appropriate for this particular cache region.

If you require fine-grained control over query cache expiration policies, you can specify a named cache region for a particular query.

Example 460. Caching query in custom region using JPA

  1. List<Person> persons = entityManager.createQuery(
  2. "select p " +
  3. "from Person p " +
  4. "where p.id > :id", Person.class)
  5. .setParameter( "id", 0L)
  6. .setHint( QueryHints.HINT_CACHEABLE, "true")
  7. .setHint( QueryHints.HINT_CACHE_REGION, "query.cache.person" )
  8. .getResultList();

Example 461. Caching query in custom region using Hibernate native API

  1. List<Person> persons = session.createQuery(
  2. "select p " +
  3. "from Person p " +
  4. "where p.id > :id")
  5. .setParameter( "id", 0L)
  6. .setCacheable(true)
  7. .setCacheRegion( "query.cache.person" )
  8. .list();

If you want to force the query cache to refresh one of its regions (disregarding any cached results it finds there), you can use custom cache modes.

Example 462. Using custom query cache mode with JPA

  1. List<Person> persons = entityManager.createQuery(
  2. "select p " +
  3. "from Person p " +
  4. "where p.id > :id", Person.class)
  5. .setParameter( "id", 0L)
  6. .setHint( QueryHints.HINT_CACHEABLE, "true")
  7. .setHint( QueryHints.HINT_CACHE_REGION, "query.cache.person" )
  8. .setHint( "javax.persistence.cache.storeMode", CacheStoreMode.REFRESH )
  9. .getResultList();

Example 463. Using custom query cache mode with Hibernate native API

  1. List<Person> persons = session.createQuery(
  2. "select p " +
  3. "from Person p " +
  4. "where p.id > :id")
  5. .setParameter( "id", 0L)
  6. .setCacheable(true)
  7. .setCacheRegion( "query.cache.person" )
  8. .setCacheMode( CacheMode.REFRESH )
  9. .list();

When using CacheStoreMode.REFRESH or CacheMode.REFRESH in conjunction with the region you have defined for the given query, Hibernate will selectively force the results cached in that particular region to be refreshed.

This behavior is particularly useful in cases when the underlying data may have been updated via a separate process and is a far more efficient alternative to the bulk eviction of the region via SessionFactory eviction which looks as follows:

  1. session.getSessionFactory().getCache().evictQueryRegion( query.cache.person );

13.7. Managing the cached data

Traditionally, Hibernate defined the CacheMode enumeration to describe the ways of interactions with the cached data. JPA split cache modes by storage (CacheStoreMode) and retrieval (CacheRetrieveMode).

The relationship between Hibernate and JPA cache modes can be seen in the following table:

Table 5. Cache modes relationships
HibernateJPADescription

CacheMode.NORMAL

CacheStoreMode.USE and CacheRetrieveMode.USE

Default. Reads/writes data from/into the cache

CacheMode.REFRESH

CacheStoreMode.REFRESH and CacheRetrieveMode.BYPASS

Doesn’t read from cache, but writes to the cache upon loading from the database

CacheMode.PUT

CacheStoreMode.USE and CacheRetrieveMode.BYPASS

Doesn’t read from cache, but writes to the cache as it reads from the database

CacheMode.GET

CacheStoreMode.BYPASS and CacheRetrieveMode.USE

Read from the cache, but doesn’t write to cache

CacheMode.IGNORE

CacheStoreMode.BYPASS and CacheRetrieveMode.BYPASS

Doesn’t read/write data from/into the cache

Setting the cache mode can be done either when loading entities directly or when executing a query.

Example 464. Using custom cache modes with JPA

  1. Map<String, Object> hints = new HashMap<>( );
  2. hints.put( "javax.persistence.cache.retrieveMode" , CacheRetrieveMode.USE );
  3. hints.put( "javax.persistence.cache.storeMode" , CacheStoreMode.REFRESH );
  4. Person person = entityManager.find( Person.class, 1L , hints);

Example 465. Using custom cache modes with Hibernate native API

  1. session.setCacheMode( CacheMode.REFRESH );
  2. Person person = session.get( Person.class, 1L );

The custom cache modes can be set for queries as well:

Example 466. Using custom cache modes for queries with JPA

  1. List<Person> persons = entityManager.createQuery(
  2. "select p from Person p", Person.class)
  3. .setHint( QueryHints.HINT_CACHEABLE, "true")
  4. .setHint( "javax.persistence.cache.retrieveMode" , CacheRetrieveMode.USE )
  5. .setHint( "javax.persistence.cache.storeMode" , CacheStoreMode.REFRESH )
  6. .getResultList();

Example 467. Using custom cache modes for queries with Hibernate native API

  1. List<Person> persons = session.createQuery(
  2. "select p from Person p" )
  3. .setCacheable( true )
  4. .setCacheMode( CacheMode.REFRESH )
  5. .list();

13.7.1. Evicting cache entries

Because the second level cache is bound to the EntityManagerFactory or the SessionFactory, cache eviction must be done through these two interfaces.

JPA only supports entity eviction through the javax.persistence.Cache interface:

Example 468. Evicting entities with JPA

  1. entityManager.getEntityManagerFactory().getCache().evict( Person.class );

Hibernate is much more flexible in this regard as it offers fine-grained control over what needs to be evicted. The org.hibernate.Cache interface defines various evicting strategies:

  • entities (by their class or region)

  • entities stored using the natural-id (by their class or region)

  • collections (by the region, and it might take the collection owner identifier as well)

  • queries (by region)

Example 469. Evicting entities with Hibernate native API

  1. session.getSessionFactory().getCache().evictQueryRegion( "query.cache.person" );

13.8. Caching statistics

If you enable the hibernate.generate_statistics configuration property, Hibernate will expose a number of metrics via SessionFactory.getStatistics(). Hibernate can even be configured to expose these statistics via JMX.

This way, you can get access to the Statistics class which comprises all sort of second-level cache metrics.

Example 470. Caching statistics

  1. Statistics statistics = session.getSessionFactory().getStatistics();
  2. CacheRegionStatistics secondLevelCacheStatistics =
  3. statistics.getDomainDataRegionStatistics( "query.cache.person" );
  4. long hitCount = secondLevelCacheStatistics.getHitCount();
  5. long missCount = secondLevelCacheStatistics.getMissCount();
  6. double hitRatio = (double) hitCount / ( hitCount + missCount );

13.9. JCache

To use the built-in integration for JCache, you need the hibernate-jcache module jar (and all of its dependencies) to be on the classpath.

In addition, a JCache implementation needs to be added as well. A list of compatible implementations can be found on the JCP website. An alternative source of compatible implementations can be found through the JSR-107 test zoo.

13.9.1. RegionFactory

The hibernate-jcache module defines the following region factory: JCacheRegionFactory.

To use the JCacheRegionFactory, you need to specify the following configuration property:

Example 471. JCacheRegionFactory configuration

  1. <property
  2. name="hibernate.cache.region.factory_class"
  3. value="jcache"/>

The JCacheRegionFactory configures a javax.cache.CacheManager.

13.9.2. JCache CacheManager

JCache mandates that CacheManagers sharing the same URI and class loader be unique in JVM.

If you do not specify additional properties, the JCacheRegionFactory will load the default JCache provider and create the default CacheManager. Also, Caches will be created using the default javax.cache.configuration.MutableConfiguration.

In order to control which provider to use and specify configuration for the CacheManager and Caches you can use the following two properties:

Example 472. JCache configuration

  1. <property
  2. name="hibernate.javax.cache.provider"
  3. value="org.ehcache.jsr107.EhcacheCachingProvider"/>
  4. <property
  5. name="hibernate.javax.cache.uri"
  6. value="file:/path/to/ehcache.xml"/>

Only by specifying the second property hibernate.javax.cache.uri will you be able to have a CacheManager per SessionFactory.

Using a non-default JCache CacheManager

If you don’t want to use the default CacheManager, you need to set the hibernate.javax.cache.cache_manager configuration property to one of the following values:

Object reference

If the value is an Object instance implementing the CacheManager interface, the provided CacheManager instance will be used.

Class

If the value is a Java Class object that implements the CacheManager interface, Hibernate will create a new instance for that Class and use it instead of the default one.

When passing a Java Class that implements the CacheManager interface, you must make sure that the CacheManager implementation class provides a default no-arg constructor because that’s going to be used to instantiate a CacheManager implementation Object.

String

If the value is a Java String, Hibernate expects it to be the fully-qualified Class name of the CacheManager implementation which will be used to instantiate the non-default CacheManager.

When passing the fully-qualified class name, you must make sure that the associated Class type provides a default no-arg constructor because that’s going to be used to instantiate a CacheManager implementation Object.

13.9.3. JCache missing cache strategy

By default, the JCache region factory will log a warning when asked to create a cache that is not explicitly configured and pre-started in the underlying cache manager. Thus if you configure an entity type or a collection as cached, but do not configure the corresponding cache explicitly, one warning will be logged for each cache that was not configured explicitly.

You may change this behavior by setting the hibernate.javax.cache.missing_cache_strategy property to one of the following values:

Table 6. Missing cache strategies
ValueDescription

fail

Fail with an exception on missing caches.

create-warn

Default value. Create a new cache when a cache is not found (see create below), and also log a warning about the missing cache.

create

Create a new cache when a cache is not found, without logging any warning about the missing cache.

Note that caches created this way may not be suitable for production usage (unlimited size and no eviction in particular) unless the cache provider explicitly provides a specific configuration for default caches.

Ehcache, in particular, allows to set such default configuration using cache templates. See the Ehcache documentation for more details.

13.10. Ehcache

This integration covers Ehcache 2.x, in order to use Ehcache 3.x as second level cache, refer to the JCache integration.

Use of the built-in integration for Ehcache requires that the hibernate-ehcache module jar (and all of its dependencies) are on the classpath.

13.10.1. RegionFactory

The hibernate-ehcache module defines two specific region factories: EhCacheRegionFactory and SingletonEhCacheRegionFactory.

EhCacheRegionFactory

To use the EhCacheRegionFactory, you need to specify the following configuration property:

Example 473. EhCacheRegionFactory configuration

  1. <property
  2. name="hibernate.cache.region.factory_class"
  3. value="ehcache"/>

The EhCacheRegionFactory configures a net.sf.ehcache.CacheManager for each SessionFactory, so the CacheManager is not shared among multiple SessionFactory instances in the same JVM.

SingletonEhCacheRegionFactory

To use the SingletonEhCacheRegionFactory, you need to specify the following configuration property:

Example 474. SingletonEhCacheRegionFactory configuration

  1. <property
  2. name="hibernate.cache.region.factory_class"
  3. value="ehcache-singleton"/>

The SingletonEhCacheRegionFactory configures a singleton net.sf.ehcache.CacheManager (see CacheManager#create()), shared among multiple SessionFactory instances in the same JVM.

The Ehcache documentation recommends using multiple non-singleton CacheManagers when there are multiple Hibernate SessionFactory instances running in the same JVM.

13.10.2. Ehcache missing cache strategy

By default, the Ehcache region factory will log a warning when asked to create a cache that is not explicitly configured and pre-started in the underlying cache manager. Thus if you configure an entity type or a collection as cached, but do not configure the corresponding cache explicitly, one warning will be logged for each cache that was not configured explicitly.

You may change this behavior by setting the hibernate.cache.ehcache.missing_cache_strategy property to one of the following values:

Table 7. Missing cache strategies
ValueDescription

fail

Fail with an exception on missing caches.

create-warn

Default value. Create a new cache when a cache is not found (see create below), and also log a warning about the missing cache.

create

Create a new cache when a cache is not found, without logging any warning about the missing cache.

Note that caches created this way may be very badly configured (large size in particular) unless an appropriate <defaultCache> entry is added to the Ehcache configuration.

13.11. Infinispan

Infinispan is a distributed in-memory key/value data store, available as a cache or data grid, which can be used as a Hibernate second-level cache provider as well.

It supports advanced functionality such as transactions, events, querying, distributed processing, off-heap and geographical failover.

For more details, check out the Infinispan User Guide.