Showing posts with label ehcache. Show all posts
Showing posts with label ehcache. Show all posts

Wednesday, December 12, 2012

Using Hibernate Query Cache in Spring

The steps for configuring the EHCache query cache is the same as when not using Spring. The main difference is how queries is being indicated as cacheable. In Spring, queries are made via the a subclass DAO of the HibernateDaoSupport class, which provides a method to indicate whether all queries in the DAO should use the query cache.

This should be called in the initDao method.

   @Override
   protected void initDao() throws Exception {
       super.initDao();

       // this enables the usage of query cache for all queries in this DAO
       this.getHibernateTemplate().setCacheQueries(true);

       // the cache region name that should be used for this entity
       this.getHibernateTemplate().setQueryCacheRegion(“XXXXXXX”);

       // optional, for enabling statistics collection for profiling
       this.getHibernateTemplate().getSessionFactory().getStatistics().setStatisticsEnabled(true);
   }


Note that this is for the query cache, not the second-level cache, the latter which is used for "get" operations.

Monday, July 2, 2012

Hibernate: Cache Invalidation Between 2 Separate Apps

Assuming the scenario where you have an admin server managing the database entries and an app server servicing user requests, and the app server is using EHCache for caching, it is possible for the Hibernate/EHCache instances to send cache invalidation messages to each other. This is a particularly useful setup as the entities in the app server might be best configured as "read-only" while the same entity in the admin server might be configured as "read-write".

For the admin server to successfully invalidate the cache on the app servers, just configure both apps to be part of the same JGroups cluster, and take note of the following issue here, and it should work fine. The classes on both ends have to be compatible of course,

Hibernate EHCache: Cache Invalidation Unable to Find SessionFactory

Using Hibernate 3.6.10 and EHCache 2.5.1, I encountered the following error in the log (in the receiving server) when a cache invalidation message was replicated from one node to another.

failed calling receive() in receiverjava.lang.IllegalArgumentException: java.lang.NullPointerException
       at org.jgroups.Message.getObject(Message.java:291)
       at net.sf.ehcache.distribution.jgroups.JGroupsCacheReceiver.
receive(JGroupsCacheReceiver.java:62)
       at org.jgroups.JChannel.up(JChannel.java:1490)
       at org.jgroups.stack.ProtocolStack.up(ProtocolStack.java:1074)
<<<<<<<<<<<<<<<<<<<<< SNIPPED >>>>>>>>>>>>>>>>>>>>>>>
       at java.util.concurrent.ThreadPoolExecutor$Worker.runTask(ThreadPoolExecutor.java:886)
       at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:908)
       at java.lang.Thread.run(Thread.java:662)
Caused by: java.lang.NullPointerException
       at java.util.concurrent.ConcurrentHashMap.get(ConcurrentHashMap.java:768)
       at org.hibernate.impl.SessionFactoryObjectFactory.getNamedInstance
(SessionFactoryObjectFactory.java:159)
       at org.hibernate.impl.SessionFactoryImpl.readResolve(SessionFactoryImpl.java:753)
       at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
       at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
       at sun.reflect.DelegatingMethodAccessorImpl.invoke
(DelegatingMethodAccessorImpl.java:25)

<<<<<<<<<<<<<<<<<<<<< SNIPPED >>>>>>>>>>>>>>>>>>>>>>>
       at java.io.ObjectInputStream.readObject(ObjectInputStream.java:350)
       at org.jgroups.util.Util.objectFromByteBuffer(Util.java:398)
       at org.jgroups.Message.getObject(Message.java:288)

The cause of this is Hibernate not being able obtain the SessionFactory instance in the receiving server. In Hibernate, it tries to obtain the SessionFactory instance using the UUID. Since this UUID was being generated randomly in both nodes, there is no chance of both UUIDs being the same. This lookup will therefore fail.

However, the fallback mechanism is to lookup using the JNDI name of the SessionFactory, which I did not set. The solution is to set the JNDI name using the "hibernate.session_factory_name" property on both nodes. After I have done that, this problem doesn't happen anymore and replication/cache invalidation is successful.

Hibernate EHCache: ClassCastException in Cluster

I have 2 Hibernate/EHCache nodes in a cluster (managed by JGroups), and after I've done some transactions, I encounter the following error:

java.lang.ClassCastException: java.util.HashMap cannot be cast to org.hibernate.cache.entry.CacheEntry

The reason for this is that one node had "hibernate.cache.use_structured_entries" set to "true" while the other node didn't have this setting configured (presumably set to "false" by default).

After ensuring that both nodes had "hibernate.cache.use_structured_entries" set to "true", this error no longer occurred.

Friday, June 1, 2012

Hibernate Caching Gotcha: Get Operations with Empty Results

When a "get" operation returns an empty result, the 2nd level cache is not populated with a cached copy of the entity. As such, the next time the same operation is performed, a query will be run on the database. If this happens a lot, it will introduce much additional load on the database.

As part of the performance optimization, clear-cut cases where empty results can happen should be eliminated.

EhCache Query Cache Usage

For each hibernate query:
q.setCacheable(true);
q.setCacheRegion(cacheRegionName); // use classname by convention

To evict the queries:
HibernateUtil.getSessionFactory().getCache().evictQueryRegion(cacheRegionName);

Enabling EH Cache for Hibernate

For some reason, Hibernate ships with a pretty old version of EH Cache, and from my experience, using a version of EH Cache that is not shipped by Hibernate doesn't always work well with Hibernate.

On the other hand, the documentation provided by EH Cache always refer to the newest version, which doesn't always work with the one shipped by Hibernate.

In a nutshell, getting Hibernate to work with EH Cache involves the following steps:
  1. Setting up EH Cache:
    1. Use the ehcache.jar file provided by Hibernate.
    2. Need to download backport-util-concurrent.jar (Latest version is 3.1 at time of writing and seems to work fine).
    3. Configure ehcache.xml and put it on the classpath. It should have a "defaultCache" element as a template and then a separate "cache" element for each entity class (with fully qualified class name as the "name" of the cache).
    4. For the query cache, add a "cache" element with name "org.hibernate.cache.StandardQueryCache".
    5. Configure cache behavior as desired.
  2. On Hibernate's side:
    1. In hibernate.cfg.xml, add the following lines (subtle differences with documentation due to older version of EH Cache used and things Hibernate did when packaging EH Cache.):
      1. <property name="cache.use_second_level_cache">true</property>
      2. <property name="cache.use_query_cache">true</property>
      3. <property name="cache.provider_class">org.hibernate.cache.SingletonEhCacheProvider</property>
    2. And then in the mapping file for each entity (in the "class" element), add (above the "id" and "property" elements):
      1. <cache usage="read-write" />
References:

Hibernate Caching Statistics

Hibernate supports statistical collection and reporting, which can be logged to profile & optimize the app's caching.

To enable it, it needs to be switched on in the session factory e.g.:
  • sessionFactory.getStatistics().setStatisticsEnabled(true);
Once that is enabled, it is possible to debug out e.g. in a Servlet Filter the statistics by calling:
  • Statistics stats = HibernateUtil.getSessionFactory().getStatistics();
  • stats.getSecondLevelCacheHitCount();
Available statistics include:
  • Second Level Cache hit/miss counts.
  • Entity load/fetch counts.
  • Query Cache hit/miss counts.
  • Query execution counts.

EHCache Config File (i.e. ehcache.xml)

Some notes about this file:
  1. The "defaultCache" element is necessary. It does not define any cache, it is just a template for any caches created.
  2. At the minimum, it needs a "defaultCache" element and a "cache" element.
  3. The "ehcache.xml" file should be placed at the top-level of the classpath. Doesn't have to be in "WEB-INF/classes". It can be in any of the jar files (e.g. in "WEB-INF/lib").
  4. The above applies in the case where only one EH Cache instance is defined (i.e. one config file). This will be the instance that will be created by default when calling "CacheManager.getInstance()". For multiple instances, the CacheManagers should be created programatically passing in the config files.
  5. "eternal" means whether the cached entries ever expire. If set to true, "timeToIdleSeconds" and "timeToLiveSeconds" settings aren't necessary.
  6. The same EH Cache instance can be used for both Hibernate and the program itself.