Pragmatic Caching – a simple Cache Configuration Model for Spring

Caching is a widely used instrument when it comes to performance tuning a given application (of course not until you’ve measured the real bottlenecks). For example, you may want to cache objects that are expensive to fetch (i.e. an object graph from database) or to calculate. Whereas most of us have used a kind of Map to ‘cache’ some data on ad hoc basis in the past, there are a couple of mature caching solutions at the market in the meantime that offer sophisticated features for caching, which goes beyond simply holding objects in memory (e.g. Thread pool controls, Element grouping, Remote server chaining (or clustering), failover, …) Deciding to use such a cache solution always comes with the question of the resulting complexity impact when applying ‘cache logic’ to your application.

Caching is an Aspect

One could rightly say, that caching is a cross cutting concern, that shouldn’t be intermingled with the rest of your business or application logic. Especially the Spring framework provides different options to cope with cross cutting concerns. Spring Modules – a sub project of the Spring framework – offers Support for declarative caching that uses Springs AOP capabilities. It provides a generic ‘API’ while supporting a bunch of different cache solutions (e.g. EHCache, JCS, OSCache, GigaSpaces, …) under the hood.

Pragmatic Caching

While its very easy to declare caching models (resp. flushing models) in a declarative (and more abstract) style with Spring Modules Caching, you sometimes may want to do it in a more programatic way due to some specific caching behaviour that’s difficult to reflect by using generic, declarative expressions. For example you may want to build a more complex cache key or decide which object to cache due to runtime conditions. In this case it may be better (and simpler) to handle caching in a more programmatic way, still treating caching as a cross cutting concern. The following solution is by far not comparable with Spring Modules Caching, yet provides a slightly different model for declaring and using a Cache. Only the core configuration and instantiation of the Cache will be managed by Spring. Caching and Flushing can be managed programmatically, giving you the full power of expressing ‘caching logic’ by the underlying language.

Pragmatic Desicions

We will use only a particular Cache solution – for example the Java Caching System (JCS), so we don’t have to cope with a variety of different Cache APIs. While Spring Modules Caching have to provide a more abstract API which is able to handle different Cache solutions, focussing on a specific solution allows us to manage cache configuration with far less code (We could of course come up with a similar abstraction of caches which allows us to write Adapters for different Cache solutions – but that’s not in the focus of this post).

Programmatic Interception

The basic concept of AOP (remember – caching is an Aspect) is method interception. In our case we want to intercept calls to service methods that provide expensive to fetch or expensive to calculate objects (and thus separating business or application logic from caching logic).
Interception is done in order to deliver objects that were cached due to prior calls (once delegated to the intercepted service).

Let’s imagine a CustomerDAO interface, which is implemented by
HibernateCustomerDAO. In order to intercept arbitrary calls to HibernateCustomerDAO, we’ll define CustomerDAOCacheInterceptor for which we’ll provide a readily configured Cache instance:


public class CustomerDAOCacheInterceptor implements CustomerDAO{

    private static final String CACHE_GROUP_VIP = "VIP";
    ...

    private JCS customerCache = null;
    private CustomerDAO target = null;

    public void setTarget( CustomerDAO target ){
        this.target = target;
    }

    public void setCustomerCache( JCS cache ){
        this.customerCache = cache;
    }
    ...

    public Customer loadCustomerByLogin( String loginId ) {

        String cacheKey = calculateCacheKey( loginId );
        String cacheGroup = getCacheGroup( loginId );

        Customer customer = (Customer)
        customerCache.getFromGroup( cacheKey, cacheGroup );

        if( customer == null ){
            customer = target.loadCustomerByLogin( loginId );

            if( customer != null ){
                try{
                    customerCache.putInGroup( cacheKey, cacheGroup, customer );
                }
                catch ( CacheException e ){
                    throw new RuntimeException(
                        "exception while trying to store Customer to cache (JCS)", e );
                }
            }
        }
        return customer;
    }

    public void update( Customer customer ) {

        target.update( Customer );

        if( someCondtionsMetOn( customer ) ){
            try{
                invalidateGroup( CACHE_GROUP_VIP );
            catch ( CacheException e ){
                throw new RuntimeException( "exception while invalidating VIP cache (JCS)", e );
            }
        }
    }
    ...
}

As you’ve seen, the Interceptor is provided with a target – the CustomerDAO implementation which is asked for a Customer in case the affected Customer isn’t cached yet (normally our HibernateCustomerDAO).
Further on, a readily configured JCS Cache instance is injected (as said before, we could easily come up a cache abstraction by shielding the Intercepor from the concrete cache instance with an appropriate cache interface), so the Interceptor doesn’t have to cope with Cache configuration and cache creation itself (maybe there are other DAOs that will use the same cache instance, so we have to come up with a central cache configuration and instantiation mechanism anyway).

Let’s say that the cache key is calculated by a more or less complex logic, that’s better described within Java. Same goes for using cache groups within the ‘customer cache’. Handling different cache groups and detecting the right cache group for a certain customer may be better placed inside the Interceptor using the full power of the language (again, the same might be true for determining conditions under which the cache or a certain cache group have to be flushed as shown in method update() ).
Note, that the demonstration doesn’t include logic to handle concurrent access – this ‘exercise’ is left to you since we only want to take a look at the caching logic.

Basic cache configuration

JCS (like most of the other cache solutions) provides a way to configure caches by using a cache configuration file (*.ccf). In it, we’ll describe the different caches which are supposed to be used within our application (for further information, please refer to the documentation):

# DEFAULT CACHE REGION
jcs.default=DC
jcs.default.cacheattributes=org.apache.jcs.engine.CompositeCacheAttributes
jcs.default.cacheattributes.MaxObjects=100
jcs.default.cacheattributes.MemoryCacheName=org.apache.jcs.engine.memory.lru.LRUMemoryCache

# PRE-DEFINED CACHE REGIONS
jcs.region.customerCache=DC
jcs.region.customerCache.cacheattributes=org.apache.jcs.engine.CompositeCacheAttributes
jcs.region.customerCache.cacheattributes.MaxObjects=200
jcs.region.customerCache.cacheattributes.MemoryCacheName=org.apache.jcs.engine.memory.lru.LRUMemoryCache
jcs.region.customerCache.elementattributes.MaxLifeSeconds=2400

jcs.region.priceCache=DC
jcs.region.priceCache.cacheattributes=org.apache.jcs.engine.CompositeCacheAttributes
jcs.region.priceCache.cacheattributes.MaxObjects=100
jcs.region.priceCache.cacheattributes.MemoryCacheName=org.apache.jcs.engine.memory.lru.LRUMemoryCache
jcs.region.priceCache.elementattributes.MaxLifeSeconds=1200

We’ve defined a default configuration and a cache coniguration for our customer cache. We could of course could have defined some more cache regions in order to cache other objects or object categories within our application (e.g. a particular region for caching Articles or calculated Prices).
That’s all we need to start. The last question (and most interesting for this post) is who’s responsible for instantiating and managing the different ‘caches’ (resp. cache regions)?

Say it with a FactoryBean

Within an application context, all we want to do is pointing to the underlying cache configuration file and choose a particular cache region (defined within the cache configuration file) by its name to bring the different cache instances to life.
This is easily done by leveraging a FactoryBean, which is responsible for the creation and management of our caches. By Injecting the path and name of the cache configuration file and the name of the cache region the FactoryBean should deliver the corresponding cache instance.

package org.common.cache;

import java.util.HashMap;
import java.util.Map;

import org.apache.jcs.JCS;
import org.apache.jcs.access.exception.CacheException;
import org.springframework.beans.factory.FactoryBean;

public class JCSCacheFactoryBean implements FactoryBean {

    private Map<String,JCS> caches = new HashMap<String,JCS>();

    private String configLocation = null;
    private String region = null;

    public void setConfigLocation(String configLocation) {
        this.configLocation = configLocation;
    }

    public void setRegion(String region) {
        this.region = region;
    }    

    public Object getObject() throws Exception {
        try{
            String cacheRegionKey =
                new StringBuffer( configLocation )
                    .append( "." ).append( "region" ).toString();

            JCS cache = null;

            if( caches.containsKey( cacheRegionKey ) ){
                cache = caches.get( cacheRegionKey );
            }
            else{
                JCS.setConfigFilename( configLocation );
                cache = JCS.getInstance( region );
                caches.put( cacheRegionKey, cache );
            }

            return cache;
        }
        catch ( CacheException e ){
            throw new RuntimeException( "exception while initializing cache (JCS)", e );
        }
    }

    public Class getObjectType() {
        return JCS.class;
    }

    public boolean isSingleton() {
        return true;
    }
}

As you’ve seen, the implementation is very straightforward. Every time a cache instance is requested, the FactoryBean will look for an appropriate cache which is already created and in memory or is creating a new instance according to the current property values configLocation and region.

A cache bean

With the given FactoryBean, it’s easy to define a ‘cache bean’ simply by providing the cache configuration file and the name of a defined cache region.
Now you can wire up dependend beans (e.g. CustomerDAOInterceptor) simply by injecting the cache bean.


    <bean id="customerDAO" class="org.sample.intercept.<span><span>CustomerDAOCacheInterceptor</span></span>">
<property name="target">
            <bean class="org.sample.CustomerDAO">
<property name="sessionFactory"><ref local="hibernateSessionFactory"/></property>
            </bean>
        </property>
<property name="customerCache"><ref bean="customerCache"/></property>
    </bean>

    <bean id="customerCache" class="org.sample.cache.JCSCacheFactoryBean">
<property name="configLocation"><value>/cache.ccf</value></property>
<property name="region"><value>customerCache</value></property>
    </bean>

    <bean id="priceCache" class="org.sample.cache.JCSCacheFactoryBean">
<property name="configLocation"><value>/cache.ccf</value></property>
<property name="region"><value>priceCache</value></property>
    </bean>
    ....
 

It’s of course possible to wire up more than one bean with a needed cache bean just as well as you can define arbitrary cache beans which represent different cache regions.

Summary

We’ve come up with a very pragmatic solution with a declarative style for cache configuration and a more programmatic style for handling caching behaviour. As always, the usefulness of such a solution depends on the given problem space and the surrounding forces.
While Spring Modules Caching allows for a more generic style for all aspects of cache confiuration and cache processing (with less code to write!) the given solution is only half-declarative (only for the configuration part) providing more power in expressing the cache processing part.

As always, its a kind of trade off – you’ll have to choose the right tool for the right situation …

16 Responses to “Pragmatic Caching – a simple Cache Configuration Model for Spring”

  1. linkblog 1 « James Law’s Joint Says:

    […] jcs cache aop with spring link […]

  2. Daily del.icio.us for April 28th through May 2nd — Vinny Carpenter’s blog Says:

    […] Pragmatic Caching – a simple Cache Configuration Model for Spring « brain driven development – We’ve come up with a very pragmatic solution with a declarative style for cache configuration and a more programmatic style for handling caching behaviour. As always, the usefulness of such a solution depends on the given problem space and the surroundi […]

  3. Weerasak.com » Article: A simple Cache Configuration Model for Spring Says:

    […] Pragmatic Caching – a simple Cache Configuration Model for Spring by Mario Gleichmann We’ve come up with a very pragmatic solution with a declarative style for cache configuration and a more programmatic style for handling caching behaviour. […]

  4. Nicolas Says:

    Nice post!
    Some questions and/or doubts:

    I see your point of making a more programatic cache instead of a fully generic one which may need certain logic to “cache” or “not cache” some objects.

    On the other hand (I know this is just an example….) you are intercepting a dao implementing its interface and you would be needing lots of classes to apply your cache in different points of your application. That could lead us to a hard to maintain app. What do you think about that?

    Nico

  5. Anonymous Says:

    I just wanted to say WOW! your site is really good and i’m proud to be one of your surfers

  6. Mario Gleichmann Says:

    Nicolas,

    thanks for your feedback!

    Yes, you are right – if you have to apply different Interceptors to different ‘Providers’ programmatically than you may end up with a lot of delegating code.

    One possible solution to reduce delegating code, could be a more generic way in that you’ll use a dynamic proxy or CGLIB and intercept (or ‘overwrite’) only those methods where you are in need of custom cache logic.

    Greetings

    Mario

  7. guddu Says:

    Hi,

    I have published some of my understanding on J2EE Design Patterns at

    http://context2designpatterns.googlepages.com

    thanks.

  8. Markus Says:

    Thanks for this post that gives us the hint, that caching should be considered from the start, because applications tend to grow and grow 😉 .

    AOP is not very helpfull for code maintenance, because your business code gets altered through compilation and eventually does not do what you expect, when you run it. You always have to have in mind, whether there is AOP code that alters the original code. But within caching it seems a good trade off. Just make sure code is well commented.

    One hint from my experience: Don’t start with database caching .. your performance may have a severe drop. Start with memory cache and put only those objects in the db, you really have to.

  9. Ankit Says:

    Hi,

    Thanks for a nice example, I have a requirement to load 20000 String objects into memory refresh them into memory once everyday.

    Is it possible using above solution ?

  10. Solution Hacker - Spring and JCS caching Says:

    […] use JCS with Spring, take a look at this article. It talks about how to create a wrapper or Interceptor for your DAO and inject it to your service […]

  11. Sachin Says:

    That guy Guddu sucks! Why does he have to post his article here? That rascal is trying to show off!

  12. anydoby Says:

    I’ve found a more pragmatic way to use caching 🙂
    You are welcome to have a look http://anydoby.com/jblog/article.htm?id=149

  13. lyates Says:

    Thanks!
    This is EXACTLY was I need and what I was looking for.

  14. Eric Dalquist Says:

    I am one of the authors of a new project intended to provide Ehcache integration for Spring 3 projects via annotations:

    http://code.google.com/p/ehcache-spring-annotations/

    The library provides two method-level annotations in the spirit of Spring’s @Transactional:

    @Cacheable
    @TriggersRemove

    When appropriately configured in your Spring application, this project will create caching aspects at runtime around your @Cacheable annotated methods.

    Usage documentation can be found on the project wiki:

    http://code.google.com/p/ehcache-spring-annotations/wiki/UsingCacheAnnotations
    http://code.google.com/p/ehcache-spring-annotations/wiki/UsingTriggersRemove

  15. Jens Says:

    Thanks Mario,
    that was just what I was looking for.

    Cheers,
    Jens


Leave a reply to Solution Hacker - Spring and JCS caching Cancel reply