Learnitweb

Second-level cache in Hibernate

1. Introduction

In this tutorial, we’ll discuss second-level (or L2) cache in Hibernate.

The persistence context is also called the first-level cache, and it’s enabled by default. But sometimes we may require to have cache data outside the context of a particular Session. The second-level cache is answer to this requirement. A second-level cache is optional and configurable. The second-level cache can be shared between applications on the same or different machines. Second-level cache is not enabled by default and need to be configured.

Following are some L2 cache implementations supported by Hibernate out of the box:

  • JCache
  • EHCache
  • Infinispan

The interface org.hibernate.cache.spi.RegionFactory defines the contract for building second level cache regions and hibernate.cache.region.factory_class property is used to declare the provider to use.

2. Caching configuration properties

Following are properties which can be used in Hibernate to control caching:

  • hibernate.cache.use_second_level_cache: This property enables or disables second level caching. If the RegionFactory is configured then the second-level cache is enabled (and if RegionFactory is not the NoCachingRegionFactory).
  • hibernate.cache.use_query_cache: This property enables or disables the second level caching of query results. The default value is false.
  • hibernate.cache.query_cache_factory: By default, stale results are not allowed. To change this behavior this property can be used.
  • hibernate.cache.use_minimal_puts: This property is used to optimize second-level cache operations to minimize writes, at the cost of more frequent reads.
  • hibernate.cache.region_prefix: This property is used to define 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 for concurrent strategies for access. However, this property is rarely used as the pluggable cache usually define their own strategy. Valid values are: read-only, read-write, nonstrict-read-write and transactional.
  • hibernate.cache.use_structured_entries: This property enables Hibernate to store data in second-level cache in user-friendly format.
  • hibernate.cache.auto_evict_collection_cache: This property enables direct storage of entity references into the second level cache for read-only or immutable entities.
  • hibernate.cache.keys_factory: This property is only supported when Infispan is configured as the second-level cache implementation. When storing entries in second-level cache, the identifier can be wrapped into tuples . This guarantees uniqueness in case all entities are stored in second-level cache. The value values are: default, simple and fully qualified class name that implements org.hibernate.cache.spi.CacheKeysFactory.

3. Configuring second-level cache mappings

By default, entities are not part of second level cache. But you can override this behavior by:

  • setting the shared-cache-mode element in your persistence.xml.
  • or by using the javax.persistence.sharedCache.mode property in configuration file.

Following are valid values:

  • ENABLE_SELECTIVE: Entities are not cacheable by default unless explicitly marked as cacheable with the @Cacheable annotation. This is the recommended setting.
  • 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.

4. Cache concurrency strategy

The global cache concurrent strategy can be set using the hibernate.cache.default_cache_concurrency_strategy property. Following are the valid properties:

  • read-only: A read-only cache is best option when the application needs to read but not modify instances of a persistent class. Application can still delete the entities.
  • read-write: If an application needs to read as well as write, a read-write cache is the option. This strategy provides a consistent access to an entity, but but not a serializable transaction isolation level.
  • nonstrict-read-write: This is similar to read-write strategy, but there might be occasional stale reads upon concurrent access to an entity. This strategy can be used when application has rare write operations.
  • transactional: Provides serializable transaction isolation level.

5. Example of Entity cache

Hibernate can cache the following:

  • Entity
  • Collection
  • Query

In this tutorial, we’ll demonstrate the entity cache. We’ll use ehCache as the pluggable cache provider.

5.1 Adding dependency for ehcache

You need to add dependencies in your pom.xml for ehcache.

	<dependency>
		<groupId>org.hibernate</groupId>
		<artifactId>hibernate-jcache</artifactId>
		<version>5.6.3.Final</version>
	</dependency>

	<!-- Ehcache 3.x -->
	<dependency>
		<groupId>org.ehcache</groupId>
		<artifactId>ehcache</artifactId>
		<version>3.3.1</version>
	</dependency>

5.2 Configuration

The next step is to enable second-level cache.

<property name="hibernate.cache.use_second_level_cache">true</property>
<property name="hibernate.cache.region.factory_class">org.hibernate.cache.jcache.JCacheRegionFactory</property>

Hibernate then uses the default JCache provider to create the default CacheManager. It also uses a default configuration to create the caches. If you want to provide a configuration, you need to specify the CacheManager and a path to the cache configuration in your persistence.xml file.

<property name="hibernate.javax.cache.provider" value="org.ehcache.jsr107.EhcacheCachingProvider"/>
<property name="hibernate.javax.cache.uri" value="file:/META-INF/ehcache.xml"/>

5.3 Make entity cacheable

@Entity
@Cacheable
@Cache(usage = CacheConcurrencyStrategy.READ_ONLY)
public class Employee {
	@Id
	@GeneratedValue(strategy = GenerationType.IDENTITY)
	@Column(name = "id")
	private Long id;

	@Column(name = "name")
	private String name;
	
	// getter and setter removed for brevity
}

In our example, Employee entity is annotated with @Cacheable and @Cache. This declares that the entity is cacheable and we have declared the cache concurrency strategy as READ_ONLY.

5.4 Test our code

In this code, we are trying to access same Employee record in two different sessions.

import org.hibernate.Session;
import org.hibernate.Transaction;

import com.learnitweb.entity.Employee;
import com.learnitweb.util.HibernateUtil;

public class Client {

	public static void main(String[] args) {
		Transaction transaction = null;
		Transaction anotherTransaction = null;
		try {
			// Create session
			Session session = HibernateUtil.getSessionFactory().openSession();
			// begin transaction
			transaction = session.beginTransaction();
			// get employee
			Employee employee = session.get(Employee.class, 1L);

			System.out.println(employee.getName());
			// commit transaction
			transaction.commit();
			// close transaction
			session.close();

			// create another session
			Session anotherSession = HibernateUtil.getSessionFactory().openSession();
			// begin another transaction
			anotherTransaction = anotherSession.beginTransaction();
			// get employee in another session
			Employee anotherEmployee = anotherSession.get(Employee.class, 1L);

			System.out.println(anotherEmployee.getName());
			// commit transaction
			anotherTransaction.commit();
			// close session
			anotherSession.close();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}

If you observe the logs, you’ll observe that the following query is executed only once.

Hibernate: select employee0_.id as id1_0_0_, employee0_.name as name2_0_0_ from Employee employee0_ where employee0_.id=?