Second level cache is an extremely powerful way to improve performance of an enterprise application if done right.
In the following I will explain how to activate and configure second level cache when using Hibernate and Jboss 7.4+ application server.
Jboss 7.4 comes with Infinispan as the build in second level cache solution. This makes it very easy to use and configure. You can get read of the library compatibility issues you can have while using other cache engines like EhCache where you must match the right version to your Jboss application server version.
By default Jboss 7.4+ comes with infinispan module activated and configured. No other libraries have to be installed or dependencies in your code.
We assume that the enterprise application persistence layer using Hibernate is configured using persistance.xml configuration file.
To activate infinispan as a second level cache you just need to add at the data source definition the following:
<persistence-unit name="default" transaction-type="JTA">
<provider>org.hibernate.jpa.HibernatePersistenceProvider</provider>
<jta-data-source>jboss/datasources/MyDS</jta-data-source>
...
<properties>
<property name="hibernate.dialect" value="org.hibernate.dialect.Oracle12cDialect" />
<property name="hibernate.hbm2ddl.auto" value="update" />
<!-- Infinispan second level cache start -->
<property name="hibernate.cache.region.factory_class" value="org.hibernate.cache.infinispan.JndiInfinispanRegionFactory" />
<property name="hibernate.cache.use_second_level_cache" value="true" />
<property name="hibernate.cache.use_query_cache" value="true" />
<property name="hibernate.cache.use_structured_cache" value="no" />
...
</properties>
where:
- hibernate.cache.region.factory_class – specifies what is the cache factory used. In the typical case of JPA we must use org.hibernate.cache.infinispan.JndiInfinispanRegionFactory
- hibernate.cache.use_second_level_cache – this will activate second level cache
- hibernate.cache.use_query_cache – this will activate the second level cache also for queries not just for entities
We may add some additional properties to change the default parameters.
<property name="hibernate.cache.infinispan.statistics" value="false"/>
<property name="hibernate.cache.infinispan.entity.eviction.strategy" value= "LRU"/>
<property name="hibernate.cache.infinispan.entity.eviction.wake_up_interval" value= "2000"/>
<property name="hibernate.cache.infinispan.entity.eviction.max_entries" value= "5000"/>
<property name="hibernate.cache.infinispan.entity.expiration.lifespan" value= "60000"/>
<property name="hibernate.cache.infinispan.entity.expiration.max_idle" value= "30000"/>
where:
- hibernate.cache.infinispan.statistics – activates statistics (false in production as adds overhead)
- hibernate.cache.infinispan.entity.eviction.strategy – Specify a custom eviction strategy. In case of most enterprise applications the best recommended one is LRU (Least recently used). Note: This parameter is deprecated in version Infinispan 5.3 so should be removed.
- hibernate.cache.infinispan.entity.eviction.wake_up_interval – wake up interval in milliseconds for eviction algorithm
- hibernate.cache.infinispan.entity.eviction.max_entries – the maximum number of entities for each entity type. This parameter is deprecated in version Infinispan 5.3 so should be removed.
- hibernate.cache.infinispan.entity.expiration.lifespan – the lifespan in milliseconds of each entity instance
- hibernate.cache.infinispan.entity.expiration.max_idle – the maximum idle time in milliseconds for each entity instance
Of course this is not enough we must add some additional properties to control what is cached and how.
For caching entities the following configuration has to be added for each entity from our application that needs to be cached.
<property name="hibernate.ejb.classcache.org.voina.core.MyEntity" value="read-write"/>
<property name="hibernate.ejb.collectioncache.org.voina.core.MyEntity.collection" value="read-write"/>
where:
- hibernate.ejb.classcache.org.voina.core.MyEntity – tells infinispan to cache entities of type org.voina.core.MyEntity. Note that only top level entities need to be specified as cache-able. If MyEntity has any child entities they will use the same cache.
- hibernate.ejb.collectioncache.org.voina.core.MyEntity.collection – tells infinispan to also cache a collection type member of an entity. Without this collection member of org.voina.core.MyEntity will not be cached.
For a query to be cache-able the following must be done:
- add a cache-able hint to the query definition.
- make sure the retuned entities are defined as cache-able.
When a query is defined inline in the code the hint can be given as:
entityManager.createQuery("select f from Foo f")
.setHint("org.hibernate.cacheable", true)
.getResultList();
When the query is defined as a named query in a separate query file:
<?xml version="1.0" encoding="UTF-8"?>
<orm:entity-mappings version="1.0" xmlns:orm="http://java.sun.com/xml/ns/persistence/orm"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/persistence/orm
http://java.sun.com/xml/ns/persistence/orm_1_0.xsd">
<orm:named-query name="findFoo">
<orm:query>
select f from Foo f
<orm:hint name="org.hibernate.cacheable" value="true" />
</orm:named-query>
...
To check if you need to cache a query or entity before the cache activation and after cache activation you should use you database of choice tools to find out queries that have a high number of executions and return static or qvasi-static data. For Oracle a good guide is https://blog.voina.be/oracle-quick-way-to-investigate-slow-or-time-consuming-application-queries/