Using Hibernate second level cache with JBoss AS and WildFly

[Updated to WildFly 10]

Hibernate is able to cache entities or queries using two distinct query mechanism. The first level cache and the second level cache. In this tutorial we will shortly recap the two kind of caches and the configuration needed to get it running with JBoss 5-6-7 and WildFly using either JPA 1 and JPA 2 annotations.

Hibernate first level cache

As we said the first level cache is associated with the current session and is used to reduce the number of SQL statements within the same transaction. It's on by default. Let's see an example :

 Session session = getSessionFactory().openSession();
 Transaction tx = session.beginTransaction();
 User user1 = (User) session.load(User.class, 1L);
 System.out.println(user1.getName());
 User user2 = (User) session.load(User.class, 1L);   
 System.out.println(user2.getName());       
 tx.commit();
 session.close();

Here, we are issuing two session.load to retrieve the User object using its primary key. Here only the first query hits the database. his can be verified by adding the following property to your persistence.xml, which displays the sql which is sent to the DB:

<property name="hibernate.show_sql" value="true" />

Note: The System.out.println calls by the way are required to force Hibernate to load data at all. If we would not put them in, nothing would get loaded. This is because data is always by default loaded lazyly in Hibernate.

Hibernate second level cache

The second level cache is used across sessions, which also differentiates it from the session cache, which only – as the name says – has session scope. Hibernate provides a flexible concept to exchange cache providers for the second-level cache. One difference with the first level cache is that it's not on by default but it needs some configuration in your persistence.xml to tell Hibernate to turn on the cache. The configuration differs depending on the version of JBoss AS used. Let's see each configuration in detail.

Using second level cache with WildFly

WildFly uses Infinispan as caching provider. Here's the suggested configuration. Here is an example persistence,xml for WildFly 10:

<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.0"
	xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://java.sun.com/xml/ns/persistence
        http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd">
	<persistence-unit name="primary">
		<jta-data-source>java:jboss/datasources/ExampleDS</jta-data-source>
		<shared-cache-mode>ENABLE_SELECTIVE</shared-cache-mode>
		<properties>
			<property name="hibernate.cache.use_second_level_cache" value="true" />
			<property name="hibernate.cache.use_query_cache" value="true" />
			<!-- Not needed any more in WildFly 10 
			    <property name="hibernate.cache.region.factory_class" 
				value="org.jboss.as.jpa.hibernate5.infinispan.SharedInfinispanRegionFactory"/> -->
			<property name="hibernate.show_sql" value="true" />
			<property name="hibernate.format_sql" value="true"/>							
			<property name="hibernate.generate_statistics" value="true" />
		</properties>
	</persistence-unit>
</persistence>

The key hibernate properties for activating Hibernate Entity Cache and Hibernate Query Cache are

  • hibernate.cache.use_second_level_cache: This enables the second level cache (2LC) which gets populated when an object is loaded by Id from the database
  • hibernate.cache.use_query_cache: This enables the Query cache which gets populated when Cachable Queries are executed

What is the relation between the 2LC and the Query Cache ?

If you execute again a Query which is able to Cache results, then no SQL statement is sent to the database. Instead the query results are fetched from the Query Cache, and then the cached Entity identifiers are used to access the second level cache.

If you execute again a find by a given Id, the Entity Cache re-hydrates the Entity and returns it. If the second level cache does not contain the results for that particular Id, then an SQL query is issued to load the entity from the database.

Some other useful properties can be used to test if the Second Level Cache is working correctly. In particular:

  • hibernate.show_sql: Will display the queries which are executed against the Database. If you are hitting the second level cache you won't see any SQL in the server's log.
  • hibernate.format_sql: This property, when set to true, will format the SQL statement executed against the Database
  • hibernate.generate_statistics: This property, when set to true, will display Cache statistics after each Statement.

Notice the JPA 2 shared-cache-mode element which is used to determine if the entity need to be cached or not. The shared-cache-mode element has four possible values:

ALL Causes all entities and entity related state and data to be cached.
NONE Causes caching to be disable for the persistence unit and all cahing is turned off for all entities
ENABLE_SELECTIVE Enables the cache and causes entities for which Cacheable(true) is specified to be cached.
DISABLE_SELECTIVE Enables the cache and causes all entities to be cached except those for which Cacheable(false) is specified

Here you can find a full example of a Java EE application which uses the Second Level Cache and the Query Cache to store Entities: https://github.com/fmarchioni/mastertheboss/tree/master/javaee/2lcache

Using second level cache with JBoss AS 6

JBoss AS 6 uses Infinispan 4 as cache provider. The required properties for persistence.xml follows here:

 <properties>
    <property name="hibernate.cache.use_second_level_cache">true</property>
    <property name="hibernate.cache.use_query_cache">true</property>
    <property name="hibernate.cache.region.factory_class">org.hibernate.cache.infinispan.JndiInfinispanRegionFactory</property>
    <property name="hibernate.cache.infinispan.cachemanager">java:CacheManager/entity</property>
 </properties>
    

The relevant part of this configuration is the cache manager which is bound in the JNDI tree under the name java:CacheManager/entity
The JNDI name to which the hibernate cache container is bound is defined in infinispan-configs.xml:

<infinispan-config name="hibernate" jndi-name="java:CacheManager/entity">
 <infinispan-config xmlns="urn:infinispan:config:4.2">
 ...
 </infinispan-config>
</persistence>

Using second level cache with JBoss AS 5

JBoss AS 5 uses JBoss Cache as caching provider. This is the configuration required to turn on the 2nd level cache on JBoss 5:

<properties>

   <property name="hibernate.cache.use_second_level_cache" value="true"/>
   <property name="hibernate.cache.use_query_cache" value="true"/>

   <property name="hibernate.cache.region.factory_class" value="org.hibernate.cache.jbc2.JndiMultiplexedJBossCacheRegionFactory"/>
   <property name="hibernate.cache.region.jbc2.cachefactory" value="java:CacheManager"/>
   <property name="hibernate.cache.region.jbc2.cfg.entity" value="mvcc-entity"/>
   <property name="hibernate.cache.region.jbc2.cfg.collection" value="mvcc-entity"/>
</properties>

By setting the property hibernate.cache.use_second_level_cache to true we are turning on the second-level cache mechanism. The cache, by default, is activated only for entities, so we also need to explicitly set hibernate.cache.use_query_cache to true if we want to cache queries as well.

The second-level cache can be implemented using several different schemas—open source and commercial. In the next property, hibernate.cache.region.factory_class, we are telling Hibernate to use JBoss Cache as the second-level cache implementation.

The next parameter, hibernate.cache.region.jbc2.cachefactory, is specific to the JBoss Cache implementation. It specifies the JNDI name under which the CacheManager to be used is bound. There is no default value, thus the user must specify the property.

The hibernate.cache.region.jbc2.cfg.collection property is also specific to JBoss Cache and details the name of the configuration that should be used for collection caches (in our configuration, mvcc-entity).


Annotations used for Caching entities

If you are using JBoss 6.x / 7.x or WildFly then you can use the JPA 2 annotation @javax.persistence.Cacheable which, used in conjunction with the shared-cache-mode, can be used to determine if the Entity should be cached or not.
The above example can be also rewritten as:

@Entity
@Cacheable
public class User {

@Id
@Column(name="key")
private String key;

@Column(name="name")
private String name;
. . . . .
}

If you are using JBoss 5.x, the annotation used for Caching entities is @org.hibernate.annotations.Cache . For example:

@Entity
@Cache (usage=CacheConcurrencyStrategy.TRANSACTIONAL)
public class Entity {

@Id
@Column(name="key")
private String key;

. . . . .
}

The CacheConcurrencyStrategy.TRANSACTIONAL provides support for Infinispan fully transactional JTA environment.  
If there are chances that your application data is read but never modified, you can apply the READ_ONLY CacheConcurrencyStrategy that does not evict data from the cache

Annotations used to cache Queries

The Query cache can be used to cache data from a query so that if the same query is issued again, it will not hit the database but return the cached value.
Note that the query cache does not cache the state of the actual entities in the result set; it caches only identifier values and results of value type.

Caching a query can be done by adding a @QueryHint annotation to a @NamedQuery, as shown in the following example:

@NamedQueries(
{
@NamedQuery(
name = "listUsers",
query = "FROM User c WHERE c.name = :name",
hints = { @QueryHint(name = "org.hibernate.cacheable", value =
"true") }  
)
})
public class User {

@Id
@Column(name="key")
private String key;

@Column(name="name")
private String name;

. . . . .
}

Follow us on Twitter