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 ifRegionFactory
is not theNoCachingRegionFactory
). - 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=?