Using DynaCache as Cache Implementation in Hibernate

Hibernate makes it very easy for us to use caching framework of our own choice. Now if you know the application that your developing is going to be deployed in WebSphere Application Server, then you might want to use the DynaCache caching framework which ships with IBM WebSphere Application Server. I wanted to try that so i did create this sample application that talks to database using Hibernate and makes use of DynaCache for caching. You can download the sample application from here In order to ask Hibernate to use DynaCache as caching implementation first thing that you will have to do is create a class that implements org.hibernate.cache.CacheProvider interface, this interface gets invoked whenever Hibernate wants to initialize cache for particular region.

package com.webspherenotes.hibernate.cache;

import java.util.Date;
import java.util.Properties;

import javax.naming.InitialContext;
import javax.naming.NamingException;

import org.hibernate.cache.Cache;
import org.hibernate.cache.CacheException;
import org.hibernate.cache.CacheProvider;

import com.ibm.websphere.cache.DistributedMap;

public class DynaCacheProvider implements CacheProvider {

  public DynaCacheProvider() throws NamingException {
    System.out.println("Entering DynaCacheProvider() constructor");

  }

  public Cache buildCache(String regionName, Properties properties)
      throws CacheException {
    System.out.println("Inside DynaCacheProvider.buildCache " + regionName);
    
    String cacheInstanceJNDIName =(String) properties.get(regionName);
    
    if(cacheInstanceJNDIName == null){
      cacheInstanceJNDIName = (String) properties.get("dynacache.default.cacheinstancename");
      if(cacheInstanceJNDIName == null){
        cacheInstanceJNDIName = "services/cache/distributedmap";
      }  
    }
    System.out.println("Cache instance JNDI name " + cacheInstanceJNDIName);
    DistributedMap distributedMap;
    DynaCache cache = null;
    try {
      InitialContext ic = new InitialContext();
      distributedMap = (DistributedMap) ic.lookup(cacheInstanceJNDIName);
      System.out.println("DistributedMap found in JNDI " + distributedMap);
      cache = new DynaCache(distributedMap, regionName);
    } catch (NamingException e) {
      e.printStackTrace(System.out);
    }


    return cache;
  }

  public boolean isMinimalPutsEnabledByDefault() {
    return false;
  }

  public long nextTimestamp() {
    return new Date().getTime();
  }

  public void start(Properties properties) throws CacheException {
  }

  public void stop() {
  }

}
,. The buildCache method is responsible for returning object of class implementing org.hibernate.cache.Cache interface that should be used for caching objects for this region. In my case i want to have granular control on which DynaCache instance should be used to store the cached object. The rule goes like this
  1. First check if there is region specific instance, if yes use that
  2. If there is no region specific instance check the value of dynacache.default.cacheinstancename to see if that's set if yes use it.
  3. The last option is to use services/cache/distributedmap which is name of the default cache instance in WAS
Next i had to create DynaCache class that implements org.hibernate.cache.Cache, the object of this class gets control for getting, setting, removing entries from cache. I am actually calling methods on DistributedMap

package com.webspherenotes.hibernate.cache;

import java.util.Map;

import org.hibernate.cache.Cache;
import org.hibernate.cache.CacheException;

import com.ibm.websphere.cache.DistributedMap;

public class DynaCache implements Cache {

  private DistributedMap map;
  private String regionName;

  public DynaCache(DistributedMap map, String regionName) {
    System.out.println("Creating object of HibernateCache() " + regionName);
    this.map = map;
    this.regionName = regionName;
  }

  public void clear() throws CacheException {
    map.clear();
  }

  public Object get(Object key) throws CacheException {
    System.out.println("Inside HibernateCache.get " + key);
    return map.get(getMapKey(key));
  }

  public String getRegionName() {
    System.out.println("Inside HibernateCache.getRegionName " + regionName);

    return regionName;
  }

  public void put(Object key, Object value) throws CacheException {
    System.out.println("Inside HibernateCache.put " + key + " " + value);
    System.out.println(value.getClass().getName());
    map.put(getMapKey(key), value);
  }

  public Object read(Object key) throws CacheException {
    System.out.println("Inside HibernateCache.read " + key);

    return map.get(getMapKey(key));
  }

  public void remove(Object key) throws CacheException {
    map.remove(getMapKey(key));
  }

  public void update(Object key, Object value) throws CacheException {
    map.put(getMapKey(key), value);
  }

  private String getMapKey(Object key) {
    return regionName + "." + key;
  }

  @Override
  public void destroy() throws CacheException {

  }

  @Override
  public long getElementCountInMemory() {
    return this.map.size();
  }

  @Override
  public long getElementCountOnDisk() {
    return 0;
  }

  @Override
  public long getSizeInMemory() {
    return 0;
  }

  @Override
  public int getTimeout() {
    return 0;
  }

  @Override
  public void lock(Object arg0) throws CacheException {

  }

  @Override
  public long nextTimestamp() {
    return 0;
  }

  @Override
  public Map toMap() {
    return this.map;
  }

  @Override
  public void unlock(Object arg0) throws CacheException {

  }

}
Last thing is to define the com.webspherenotes.hibernate.cache.DynaCacheProvider as cacheProvider in hibernate.cfg.xml this tells the Hibernate framework to use the DynaCacheProvider that we just created.

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-configuration PUBLIC
        "-//Hibernate/Hibernate Configuration DTD 3.0//EN"
        "http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd">

<hibernate-configuration>

    <session-factory>

        <!-- Database connection settings -->
        <property name="connection.driver_class">org.apache.derby.jdbc.ClientDriver</property>
        <property name="connection.url">jdbc:derby://localhost:1527/C:/data1/contact</property>
        <property name="connection.username">dbadmin</property>
        <property name="connection.password">dbadmin</property>

        <!-- JDBC connection pool (use the built-in) -->
        <property name="connection.pool_size">1</property>

        <!-- SQL dialect -->
        <property name="dialect">org.hibernate.dialect.DerbyDialect</property>

      
       <property name="cache.provider_class">com.webspherenotes.hibernate.cache.DynaCacheProvider</property>
       <property name="dynacache.default.cacheinstancename">services/cache/distributedmap</property>
       <property name="com.javaworld.memcache.Contact">services/cache/contact</property>
       
       <property name="hibernate.cache.use_second_level_cache">true</property>
       
         <property name="cache.use_query_cache">true</property>
       <property name="">localhost:11211</property>

        <!-- Echo all executed SQL to stdout -->
        <property name="show_sql">true</property>

        <!-- Drop and re-create the database schema on startup 
        <property name="hbm2ddl.auto">create</property>-->

        <mapping resource="com/javaworld/memcache/Contact.hbm.xml"/>
    <mapping resource="com/javaworld/memcache/Address.hbm.xml"/>
    </session-factory>

</hibernate-configuration>
In this file i am telling hibernate to use service/cache/contact cache instance for caching Contact object but the Address objects would get stored in the default cache. So after deploying the code i did hit the cache few times and this what i see in the cachemonitor

3 comments:

Marcelo said...

Hi,
great post.
I will try this out.
Unfortunatly the download link is not working

Sunil Patil said...

Hi Marcelo,

I tried to look for the sample but not able to find the code on my machine and it seems that i forgot to upload the sample. But i think you can simply copy the code from the blog entry in your app and it should work

Marcelo said...

Hi,

yes it works :-).
However the way to implement an third party cache has changed massivly in hibernate 4.
So its necessary to rebuild the Adapter.
What a mess .-(