How to enable level 2 cache for Hibernate JPA in WildFly

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 WildFly and older JBoss AS using either JPA 1 and JPA 2 annotations.

Hibernate first-level cache

The first-level cache provides application-level repeatable reads. It’s not a caching solution “per se”, but it’s useful for ensuring READ COMMITTED isolation level.

Technically, 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" />

The first-level cache is short-live: it is cleared as soon as you close the underlying EntityManager.

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 the application server. Let’s see them in detail:

Using second level cache with WildFly

WildFly uses Ias caching provider. Here’s the suggested configuration. Here is an example persistence.xml for WildFly:

<?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" />

            <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>

Notice the shared-cache-mode element which is used to determine if the entity needs 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 disabled for the persistence unit and all caching 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

Therefore, if you use ENABLE_SELECTIVE as shared-cache-mode, only Entities configured as @javax.persistence.Cacheable / @org.hibernate.annotations.Cache will be eligible to be stored in the Second Level Cache:

@Entity
@Cacheable
public class Customer {

}

On the opposite, DISABLE_SELECTIVE disables caching only for entities marked with either
@javax.persistence.Cacheable(false) or
@org.hibernate.annotations.Cache(usage=org.hibernate.annotations.CacheConcurrencyStrategy.NONE)

The other parameter, namely hibernate.cache.use_query_cache, can be used to activate the Query cache. The query cache is optional and can be used whenever you need to access your data in a more flexible way other than the primary key.

The following example shows how you can activate the Query cache on an Entity, by setting the org.hibernate.cacheable to true:

@Entity
@Cacheable
@NamedQueries({
@NamedQuery(
name = "findAll",
query = "FROM Customer c",
hints = { @QueryHint(name = "org.hibernate.cacheable", value =
"true") }
)
})
public class Customer {

}

When to use the second-level Cache

Even if the second-level cache can reduce database round trips since entities are retrieved from the cache rather than from the database, there are other options to achieve the same goal. Therefore, you should consider these alternatives before jumping headlong in the second-level cache layer:

  • Tune the Database cache so that your working set fits into memory. This will greatly reduce Disk I/O traffic.
  • Optimize the SQL statements through JDBC batching, statement caching, indexing can reduce the average response time, therefore increasing throughput as well.
  • Database replication should be taken into account to increase read-only transaction throughput

After properly tuning the database, to further reduce the response time while increasing the throughput, application-level caching should be added.

Monitoring the second-level Cache

Using the second-level cache can easily lead to extensive memory usage therefore it’s fundamental to check if it’s actually helpful for the performance of your application. You can monitor the performance of your second level cache at deployment level or at Entity level.

The example below shows how to gather 2nd level cache metrics for the Web application demojpa.war:

/deployment=demojpa.war/subsystem=jpa/hibernate-persistence-unit=unit01#primary:read-resource(include-runtime=true, recursive=true)
{
    "outcome" => "success",
    "result" => {
        "close-statement-count" => 0L,
        "collection-fetch-count" => 1L,
        "collection-load-count" => 1L,
        "collection-recreated-count" => 27L,
        "collection-remove-count" => 1L,
        "collection-update-count" => 0L,
        "completed-transaction-count" => 151L,
        "connect-count" => 312L,
        "enabled" => true,
        "entity-delete-count" => 3L,
        "entity-fetch-count" => 4L,
        "entity-insert-count" => 58L,
        "entity-load-count" => 215L,
        "entity-update-count" => 4L,
        "flush-count" => 88L,
        "hibernate-persistence-unit" => "unit01#primary",
        "optimistic-failure-count" => 0L,
        "prepared-statement-count" => 294L,
        "query-cache-hit-count" => 0L,
        "query-cache-miss-count" => 0L,
        "query-cache-put-count" => 0L,
        "query-execution-count" => 277L,
        "query-execution-max-time" => 6L,
        "query-execution-max-time-query-string" => "select customername0 from Customer as cname0 order by customerCountry1 desc",
        "second-level-cache-hit-count" => 112L,
        "second-level-cache-miss-count" => 4L,
        "second-level-cache-put-count" => 66L,
        "session-close-count" => 245L,
        "session-open-count" => 245L,
        "statistics-enabled" => true,
        "successful-transaction-count" => 245L,
        ...

The second-level-hit-count and query-cache-hit-count attributes need to be compared with their counterpart metrics, that is second-level-miss-count and query-cache-miss-count. The bigger is the ratio between those factors (hit/miss), the better is performing your second level cache and query cache.

For the sake of completeness, we will mention that it is also possible to check the single Entity’s metrics as follows:

/deployment=demojpa.war/subsystem=jpa/hibernate-persistence-unit=unit01#primary/entity-cache=com.demo.Customer:read-resource(include-runtime=true, recursive=true)
{
    "outcome" => "success",
    "result" => {
        "entity-cache-region-name" => "entity-cache-region-name",
        "second-level-cache-count-in-memory" => 15L,
        "second-level-cache-hit-count" => 41L,
        "second-level-cache-miss-count" => 1L,
        "second-level-cache-put-count" => 22L,
        "second-level-cache-size-in-memory" => -1L
    }
}

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.

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;

. . . . .
}