Caching with EhCache – Part II

In this part of the article about caching we'll discuss using EhCache for a different purpose other than as Hibernate second level cache.

Before working with EhCahe, I used a static variable for a cache. For example, I would have:

public class MemoryStore {  
    public static List<Player> players;
}

//A main class
public class Main {  
    public static void main(String[] args) {
        ClassPathXmlApplicationContext appContext =
                new ClassPathXmlApplicationContext(new String[] {
                        "applicationContext.xml"
                });

        List<Player> lstPlayers = remoteService.getAllPlayers(); //Just a costly method that would justify using a cache

        MemoryStore.players = lstPlayers; //init the static, as early as possible as not to have

        ....
        //Continue further to the business code implementation and the part that would use the MemoryStore.players
    }
}

What are the problems we may encounter when using this setup:

  • Make sure we init the static variable before we use it. Do not start other threads that may use this variable until it has been set.
  • What about refreshing the value of this "cache" after some time? Solution - maybe have another thread that "expires" and refreshes the static cache variables after some time.
  • Do we need to worry about synchronization if a get occurs while we update the cache? We may ignore it and hope that the cache is used rarely that there are very small chances of a read and write ever overlapping. Otherwise since we would not want to synchronize the reads(we would not want that reads block other reads), we could use a ReentrantLock for example, so that reads not block other reads, but writes block reads.

As we can see, although not hard to implement, this classic approach has some disadvantages that come with it.

Let's modify this example to use EhCache instead of the static variable. We would first get a hold of the cache manager. From the cache manager we can get a reference of an already existing cache, or add a new cache.

Remember the SingletonCacheManager from Part I? We can still have the cache configuration for hibernate and in the same ehcache.xml file declare our own cache. Let's call it the "customCache" cache.

<cache name="customCache" maxElementsInMemory="30" eternal="false" timeToLiveSeconds="60" overflowToDisk="false"/>  

It would be nice that the cache knows how to populate itself and get around the first problem of having to populate it at the beginning of the application. Since it would be an EhCache type of cache, it would have the properties of the cache configured in the ehcache.xml file, and we are especially interested in the expiration property if we want to refresh this cache at some interval. The cache knows how to populate itself, after expiration it would again use the method to retrieve the new values, so the second problem of expiration would be solved.

EhCache offers the class SelfPopulatingCache that extends BlockingCache class. The BlockingCache class is a cache decorator, which allows concurrent read access to elements already in the cache. If the element is null, other reads will block until an element with the same key is put into the cache. This means that we solved our last issue with the old way of using the static variable for a holder and not having to worry about synchronization.

Let's see it in action:

CacheManager cacheManager = CacheManager.getInstance();

Cache customCache = (Cache) cacheManager.getCache("customCache");

SelfPopulatingCache selfPopulatingCache = new SelfPopulatingCache(customCache, new CacheEntryFactory() {  
public Object createEntry(Object key) throws Exception {  
if("players".equals((String)key)) {  
     List<Player> lstPlayers = remoteService.getAllPlayers();
     return lstPlayers;
}
return null;  
}

cacheManager.replaceCacheWithDecoratedCache(customCache,  
selfPopulatingCache); //this method does what the name implies and from now on any call to cacheManager.getEhCache("customCache") will return the SelfPopulatingCache. Be carefull to not call cacheManager.getCache("customCache") since this method will not return null, and not the decorated cache.


List players = customCache.get("players"); //This first call invokes the createEntry method and the cache is populated

customCache.get("players"); //When this gets called the data is pulled from the cache and the createEntry method do not gets called

//After 60 seconds - the value of the timeToLiveSeconds passes
customCache.get("players"); //this call will block until createEntry(Object key) which gets called, will finish repopulating the cache  

for example:

        CacheManager cacheManager = CacheManager.getInstance();

        Cache customCache = (Cache) cacheManager.getCache("customCache");

        SelfPopulatingCache selfPopulatingCache = new SelfPopulatingCache(customCache, new CacheEntryFactory() {
           public Object createEntry(Object key) throws Exception {
               log.info("*** Create entry is being called ***");

               if("players".equals((String)key)) {
                   List<Player> playersList = new ArrayList<Player>();

                   playersList.add(new Player(1, "John"));
                   playersList.add(new Player(2, "Serban"));
                   playersList.add(new Player(3, "Weasley"));

                   return playersList;
               }

               return null;
           }
        });

        cacheManager.replaceCacheWithDecoratedCache(customCache, 
selfPopulatingCache);

        log.info("Before first call");
        List players = (List) selfPopulatingCache.get("players").getObjectValue();
        log.info("Players " + players.size());

        log.info("Before second call");
        players = (List) selfPopulatingCache.get("players").getObjectValue();
        log.info("Players " + players.size());

        try {
            Thread.sleep(70 * 1000); //sleep so we expire the elements
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        log.info("Before third call - after expired");
        players = (List) selfPopulatingCache.get("players").getObjectValue();
        log.info("Players " + players.size());

        //We put another element in the cache just to show that we can
        selfPopulatingCache.put(new Element("coach", "Robin Hood"));

And the logs:


2009-12-15 15:20:51,609 INFO com.balamaci.Main - Before first call
2009-12-15 15:20:51,609 DEBUG net.sf.ehcache.Cache - customCacheCache: customCacheMemoryStore miss for players
2009-12-15 15:20:51,609 DEBUG net.sf.ehcache.Cache - customCache cache - Miss
2009-12-15 15:20:51,609 INFO com.balamaci.Main - * Create entry is being called
2009-12-15 15:20:51,625 INFO com.balamaci.Main - Players 3
2009-12-15 15:20:51,625 INFO com.balamaci.Main - Before second call
2009-12-15 15:20:51,625 DEBUG net.sf.ehcache.Cache - customCacheCache: customCacheMemoryStore hit for players
2009-12-15 15:20:51,625 INFO com.balamaci.Main - Players 3
2009-12-15 15:22:01,625 INFO com.balamaci.Main - Before third call - after expired
2009-12-15 15:22:01,625 DEBUG net.sf.ehcache.Cache - customCache Memory cache hit, but element expired
2009-12-15 15:22:01,625 DEBUG net.sf.ehcache.Cache - customCache cache - Miss
2009-12-15 15:22:01,625 INFO com.balamaci.Main -
Create entry is being called *
2009-12-15 15:22:01,625 INFO com.balamaci.Main - Players 3

We can see in the log the cache being populated as a result of the get call. The cache is not being populated until this first call. For the second call we have a cache hit and the list of players from the cache is returned. After waiting longer than the timeToLiveSeconds parameter, the third call finds the elements expired, so the cache is repopulated by calling again the createEntry method.



As you browse through the sources you may see that there is another class that extends BlockingCache, the UpdatingSelfPopulatingCache, an extension of the SelfPopulatingCache. This class adds the updateEntryValue(Object key, Object value) method which for some reason seems to be called every time a customCache.get(key) method is invoked. I do not see any value for a cache class that we expect to call an update method for every cache read, whoever I'll still put up an example of usage:

UpdatingSelfPopulatingCache updatingCache = new UpdatingSelfPopulatingCache(teamsCache,  
      new UpdatingCacheEntryFactory() {
            public void updateEntryValue(Object key, Object value) throws Exception {
                log.info("Updating entry for key " + key);
                if(key == 1) {
                    Player player = (Player ) value;
                    player.setName("Smith");
                }
            }

            public Object createEntry(Object key) throws Exception {
                log.info("Creating entry for key " + key);
                if((Integer) key == 1) {
                    return new Player(1, "John");
                }

                return null;
            }
   });
   cacheManager.replaceCacheWithDecoratedCache(customCache, updatingCache);

   log.info("Before first call");
   Player player = (Player) updatingCache.get(1).getObjectValue();
   log.info("Got Player " + player);

   log.info("Before second call");
   player = (Player) updatingCache.get(1).getObjectValue();
   log.info("Got Player " + player);

We see that the element is refreshed:

2009-12-15 18:37:14,765 DEBUG net.sf.ehcache.Cache - customCacheCache: customCacheMemoryStore miss for 1
2009-12-15 18:37:14,765 DEBUG net.sf.ehcache.Cache - customCache cache - Miss
2009-12-15 18:37:14,765 INFO com.balamaci.Main - Creating entry for key 1
2009-12-15 18:37:14,765 INFO com.balamaci.Main - Got Player Id=1 Name=John
2009-12-15 18:37:14,765 INFO com.balamaci.Main - Before second call
2009-12-15 18:37:14,765 DEBUG net.sf.ehcache.Cache - customCacheCache: customCacheMemoryStore hit for 1
2009-12-15 18:37:14,765 DEBUG net.sf.ehcache.constructs.blocking.SelfPopulatingCache - customCache: refreshing element with key 1
2009-12-15 18:37:14,765 INFO com.balamaci.Main - Updating entry for key 1
2009-12-15 18:37:14,765 INFO com.balamaci.Main - Got Player Id=1 Name=Jersey

As you can see, the second get call triggers an update which I do not understand why it could be useful. Perhaps some comments would help.



In conclusion, you can choose to use EhCache to store your own data, not just use it as a Hibernate cache and you'll have the benefit of "out of the box" expiration of elements and repopulation, synchronization, access statistics and if you need overflow to disk.

Another interesting feature of EhCache is the ability to add a cache event listener. By adding a cache event listener to a cache, you can be notified when an element has been added, removed, or expired. To add a custom cache event listener you need to create a class that extends the CacheEventListener and a factory method that extends CacheEventListenerFactory abstract class which will return your custom cache event listener. You can assign this factory to the cache in the ehcache.xml file like this:

<cache name="customCache" maxElementsInMemory="30" eternal="false" timeToLiveSeconds="60" overflowToDisk="false">  
        <cacheEventListenerFactory class="com.balamaci.MyCacheEventListenerFactory"/>
</cache>  

This is the base they are building upon for registering cache statistics, and for making a distributed cache - every put, update or remove is caught and through different means and protocols replicated to another instance of EhCache-.

In Part III of the EhCache tutorials, I'll be planning to talk about how we can make a distributed cache, to be accessed by another application even on a remote computer.

comments powered by Disqus