Caching Made Easy with Spring

5 years ago by in Articles, Spring Tagged: , , , ,

Spring 3.1 introduced a new and simply way to cache results. In this article we will see how we can use the new Spring caching within our projects in order to avoid repeating task for which outcome was already produced. The readers of this article are expected to have some basic knowledge about Spring and dependency injection (or as it also referred to: inversion of control).

All code listed below is available at: http://code.google.com/p/java-creed-examples/source/checkout. Most of the examples will not contain the whole code and may omit fragments which are not relevant to the example being discussed. The readers can download or view all code from the above link.

This article is divided three sections. In the first section we see a simple example, just enough to get us going. In the second section we look under the hood and see a how the caching works with respect to recursion. In the last section we will see a simple, but real-world like, example where caching is applied. In the last section, we will also see how to clear the cache when this is expired or invalid.

Caching with Spring

Consider the following class.

package com.javacreed.examples.sc.part1;

import org.springframework.stereotype.Component;

@Component
public class Worker {

  public String longTask(final long id) {
    System.out.printf("Running long task for id: %d...%n", id);
    return "Long task for id " + id + " is done";
  }

  public String shortTask(final long id) {
    System.out.printf("Running short task for id: %d...%n", id);
    return "Short task for id " + id + " is done";
  }
}

Here we have a simple Spring component class that has two methods. One method, named longTask(), carries a fictitious long task, while the second method, named shortTask(), runs quickly. The output of both methods is only determined by the input of these methods. Therefore, for the same input we will always get the same output. This is very important, as otherwise we cannot apply caching.

Now consider the following class.

package com.javacreed.examples.sc.part1;

import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Main {

  public static void main(final String[] args) {
    final String xmlFile = "META-INF/spring/app-context.xml";
    try (ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(xmlFile)) {

      final Worker worker = context.getBean(Worker.class);
      worker.longTask(1);
      worker.longTask(1);
      worker.longTask(1);
      worker.longTask(2);
      worker.longTask(2);
    }
  }
}

Here we are creating the Spring environment and retrieving an instance of our Worker from Spring. Then we invoke the method longTask() five times. This will produce the following output to the command prompt.

Running long task for id: 1...
Running long task for id: 1...
Running long task for id: 1...
Running long task for id: 2...
Running long task for id: 2...

Note that the method longTask() runs five times, one for every request. Also note that this method only received two different inputs. This method is invoked three times with the parameter value: 1 and twice with the parameter value: 2. Since the output of this fictitious long task method is only determined by its input, we can cache the output and on the next request for this input we use the cached value instead of re-running this fictitious long task.

In order to apply caching we need to do the following three things.

  1. Mark the methods (or classes) which output will be cached.
    Spring 3.1 added new annotations that enable method caching as shown next.

    package com.javacreed.examples.sc.part1;
    
    import org.springframework.cache.annotation.Cacheable;
    import org.springframework.stereotype.Component;
    
    @Component
    public class Worker {
    
      @Cacheable("task")
      public String longTask(final long id) {
        System.out.printf("Running long task for id: %d...%n", id);
        return "Long task for id " + id + " is done";
      }
    
      public String shortTask(final long id) {
        System.out.printf("Running short task for id: %d...%n", id);
        return "Short task for id " + id + " is done";
      }
    
    }
    

    By simply adding the @Cacheable annotation (Java Doc) to the method signature, repetitive requests to this method, with the same parameter value, will simply return the cached value. Spring allows us to cache values without having to write the boilerplate-code that handles that. Note that this annotation also takes a value, which is the name of the cache repository. We will talk about the cache repository in a minute.

    Observation

    Note that the annotation @Cacheable can be applied to a class, which means that all methods of that class are cached.

  2. Enabled Spring caching.
    Before Spring can start caching our values, we need add the following declaration.

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xmlns:context="http://www.springframework.org/schema/context"
      xmlns:cache="http://www.springframework.org/schema/cache"
      xmlns:p="http://www.springframework.org/schema/p"
      xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
        http://www.springframework.org/schema/cache http://www.springframework.org/schema/cache/spring-cache-3.2.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.1.xsd">
    
      <context:annotation-config />
      <context:component-scan base-package="com.javacreed.examples.sc" />
    
      <!-- Enables the caching through annotations -->
      <cache:annotation-driven />
    
    </beans>
    

    With this declaration, Spring will look for any classes or methods that are marked cacheable and will take all necessary actions in order to provide caching.

  3. Configure the caching repository to be used.
    Before this code can work, we need to define the cache repository as shown next.

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xmlns:context="http://www.springframework.org/schema/context"
      xmlns:cache="http://www.springframework.org/schema/cache"
      xmlns:p="http://www.springframework.org/schema/p"
      xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
        http://www.springframework.org/schema/cache http://www.springframework.org/schema/cache/spring-cache-3.2.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.1.xsd">
    
      <context:annotation-config />
      <context:component-scan base-package="com.javacreed.examples.sc" />
    
      <!-- Enables the caching through annotations -->
      <cache:annotation-driven />
    
      <!-- Generic cache manager based on the JDK ConcurrentMap -->
      <bean id="cacheManager" class="org.springframework.cache.support.SimpleCacheManager">
        <property name="caches">
          <set>
            <bean class="org.springframework.cache.concurrent.ConcurrentMapCacheFactoryBean" p:name="task" />
          </set>
        </property>
      </bean>
    </beans>
    

    The cache repository is where the actual objects are saved. Spring supports two types of repositories: one based on JDK ConcurrentMap and the other on ehcache popular library. More can be added. Here we are using the JDK ConcurrentMap as our cache repository. The repository plays little (if any) effect on the code and switching between repositories should be very easy. Our objects will be cached within a ConcurrentMap. In this example we added one cache repository, named task. We can have more than one repository. Note that the name of this repository is the same as the name showed in the annotation before.

    Observation

    Please note that the JDK ConcurrentMap class shown in the above declaration varies between versions of Spring 3.1 and 3.2. Here we are using Spring 3.2. In version 3.1 the class name is as shown below.

    org.springframework.cache.concurrent.ConcurrentCacheFactoryBean
    

How does this work?

Spring, when configured to use caching, will wrap the objects that are marked to be cached in to a proxy. The caller will not work with our object but with the proxy instead as shown in the following figure.

Caching Proxy

Caching Proxy

If we had to print the class’s canonical name of the object returned by the Spring environment, we will see the following.

Worker class: com.javacreed.examples.sc.part1.Worker$$EnhancerByCGLIB$$4fa6f80b

Note that this is not the Worker class (canonical name: com.javacreed.examples.sc.part1.Worker) we created but some other class. In fact, this class was generated by Spring using code generation techniques, which are not discussed here. When we invoke any methods from the Worker class, instead we are calling the methods in the generated proxy. This proxy holds an instance of our Worker class. It will forward any request to our object and return its response as shown in the following figure. If the method is marked as cacheable, then the proxy will bypass the request and will return the cached value instead. If the proxy does not have a cached value yet for the given input, it makes the request and saves the response for future use.

Proxy Flow of Control

Proxy Flow of Control

If we run again our Main class, we will get the following output.

Running long task for id: 1...
Running long task for id: 2...

Here the fictitious long task method (longTask()) is actually invoked twice. The proxy returned the cached result for the other times. This concludes our first section about Spring caching. As we saw, it is quite easy to enable. All we need to do is follow the three steps listed before, and we have caching. In the next section we will see how we can apply caching with recursion.

Caching a recursive method

Consider the following class.

package com.javacreed.examples.sc.part2_1;

import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Component;

@Component("fibonacci")
public class Fibonacci {

  private int executions = 0;

  public int getExecutions() {
    return executions;
  }

  public void resetExecutions() {
    this.executions = 0;
  }

  @Cacheable("fibonacci")
  public long valueAt(final long index) {
    executions++;
    if (index < 2) {
      return 1;
    }

    return valueAt(index - 1) + valueAt(index - 2);
  }

}

This class implements Fibonacci sequence and returns the Fibonacci number at a given index. The Fibonacci number is calculated recursively using the function: fib(n) = fib(n-1) + fib(n-2). The base case for this recursive function is that the first two Fibonacci numbers are 1.

Note that this class also keeps track of how many times the valueAt() method is invoked. We can obtain this value through the getter method. The Fibonacci class also enabled resetting of this value so that the counter starts from 0 once more.

Here everything seems to be in place and the class is enabled for caching. So let us execute it.

package com.javacreed.examples.sc.part2_1;

import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Main {

  public static void main(final String[] args) {
    final String xmlFile = "META-INF/spring/app-context.xml";
    try (ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(xmlFile)) {

      final long start = System.nanoTime();
      final Fibonacci sequence = context.getBean("fibonacci", Fibonacci.class);
      final long fibNumber = sequence.valueAt(5);
      final int executions = sequence.getExecutions();
      final long timeTaken = System.nanoTime() - start;
      System.out.printf("The 5th Fibonacci number is: %d (%,d executions in %,d NS)%n", fibNumber, executions,
          timeTaken);
    }
  }
}

If we run the above code, we will get the following output.

The 5th Fibonacci number is: 8 (15 executions in 17,762,022 NS)

The cacheable method valueAt() was invoked a total of 15 times. This does not sound right. The valueAt() method should have been executed only 6 times and not 15 times. The other times 9 times, the cached value should have been returned instead.

What went wrong?
In the main() method we obtained an instance of the Fibonacci class through Spring. In turn, Spring wrapped our object into a proxy. Therefore within the main() method, we only have access to the proxy. But the valueAt() method within the Fibonacci class, calls itself (recursion). This is not calling the valueAt() method through the proxy, but directly from Fibonacci class. Therefore the proxy is bypassed. That is why we are not caching the value at the recursion level.

Observation

Note that if we had to invoke the sequence.valueAt(5); (with the same value) again, the cached value will be returned as the variable sequence is an instance of the proxied Fibonacci.

How can we fix this?
In order to fix this we need to modify the Fibonacci class and pass a reference of our proxy as shown next.

package com.javacreed.examples.sc.part2_2;

import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Component;

@Component("fibonacci2")
public class Fibonacci {

  private int executions = 0;

  public int getExecutions() {
    return executions;
  }

  public void resetExecutions() {
    this.executions = 0;
  }

  @Cacheable("fibonacci")
  public long valueAt(final long index, final Fibonacci callback) {
    executions++;
    if (index < 2) {
      return 1;
    }

    return callback.valueAt(index - 1, callback) + callback.valueAt(index - 2, callback);
  }

}

Note that now our valueAt() method takes two parameters and not one. It takes an instance of the Fibonacci class, referred to as callback. Furthermore, instead of invoking the valueAt() on itself, it invokes the callback‘s valueAt(). From the main() method, we need to also pass an instance of the proxied Fibonacci class as shown in the following example.

package com.javacreed.examples.sc.part2_2;

import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Main {

  public static void main(final String[] args) {
    final String xmlFile = "META-INF/spring/app-context.xml";
    try (ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(xmlFile)) {

      final long start = System.nanoTime();
      final Fibonacci sequence = context.getBean("fibonacci2", Fibonacci.class);
      final long fibNumber = sequence.valueAt(5, sequence);
      final int executions = sequence.getExecutions();
      final long timeTaken = System.nanoTime() - start;
      System.out.printf("The 5th Fibonacci number is: %d (%,d executions in %,d NS)%n", fibNumber, executions,
          timeTaken);
    }
  }
}

This class will produce the following output.

The 5th Fibonacci number is: 8 (6 executions in 18,320,003 NS)

Observation

Note that this example, took longer than the non-cached version. This is because caching adds some overheads, and this example did not benefit enough to overcome these costs. If instead we try to retrieve a larger Fibonacci number, such as 50, then the benefit will appear. Please do not try very large numbers as these will take a considerable amount of time to compute.

This concludes our example with caching and using recursion. The key point is that recursion will bypass the proxy unless a callback is specified. In the following section we will see a simple, real world like, example.

Real-world like example

Consider the following domain class.

package com.javacreed.examples.sc.part3;

public class Member {

  private final int memberId;
  private final String memberName;

  public Member(final int memberId, final String memberName) {
    this.memberId = memberId;
    this.memberName = memberName;
  }

  // Getters removed for brevity

  @Override
  public String toString() {
    return String.format("[%d] %s", memberId, memberName);
  }
}

This is a simple class that represents a member having only an id, which uniquely identifies a member and a name. For simplicity, the members are persisted in a text file of the format shown next.

1,Albert Attard
2,Mary Borg
3,Tony White
4,Jane Black

Now consider the following service interface.

package com.javacreed.examples.sc.part3;

public interface MembersService {

  Member getMemberWithId(int id);

  void saveMember(Member member);
}

This interface exposed two methods one used to retrieve the member with the given id and the other to persist any modifications made to file. The following class starts the whole thing and makes several request to the MembersService implementation. Note that the implementation is cacheable and therefore we are working with the proxy version of the MembersService.

package com.javacreed.examples.sc.part3;

import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Main {

  public static void main(final String[] args) {
    final String xmlFile = "META-INF/spring/app-context.xml";
    try (ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(xmlFile)) {

      final MembersService service = context.getBean(MembersService.class);

      // Load member with id 1
      Member member = service.getMemberWithId(1);
      System.out.println(member);

      // Load member with id 1 again
      member = service.getMemberWithId(1);
      System.out.println(member);

      // Edit member with id 1
      member = new Member(1, "Joe Vella");
      service.saveMember(member);

      // Load member with id 1 after it was modified
      member = service.getMemberWithId(1);
      System.out.println(member);
    }
  }
}

If we execute the above, the following is produced to the command prompt.

Retrieving the member with id: [1] from file: C:\javacreed\spring-cache\members.txt
[1] Albert Attard
[1] Albert Attard
Retrieving the member with id: [1] from file: C:\javacreed\spring-cache\members.txt
[1] Joe Vella

Here we made two requests to retrieve the member with id 1, but the method was actually invoked once. In the second request, the cached value was returned. Then we modified the member with the same id. Since the member was modified, the cache was invalidated (we will see how shortly). Thus when retrieving the member with the same id, we invoked the actual method again and load it from file. This value will be cached until invalidated again.

Now let us see how this is implemented. The getMemberWithId() is similar to the other methods we saw already. It is annotated with the @Cacheable annotation.

  @Override
  @Cacheable("members")
  public Member getMemberWithId(final int id) {
    System.out.printf("Retrieving the member with id: [%d] from file: %s%n", id, dataFile.getAbsolutePath());
   // code removed for brevity
  }

The saveMember() needs to invalidate the cache. In order to achieve this, Spring provides another annotation named: @CacheEvict (Java Doc) as shown next.

  @Override
  @CacheEvict(value = "members", allEntries = true)
  public void saveMember(final Member member) {
   // code removed for brevity
  }

Whenever this method is invoked, the cache repository named members is cleared from all members (as instructed by the: allEntries = true annotation optional parameter). Therefore, next time the getMemberWithId() method is invoked it will have to load the member from file, thus reading the new changes. Without this, the getMemberWithId() method will still return the old version of the member with id 1.

This concludes our article about Caching with Spring. In this article we saw how we can leverage existing API in order to cache results without having to write any boilerplate code.

Albert Attard

Albert Attard is a Java passionate and technical lead at a research group. You can find him on . Over the past years Albert worked on various Java projects including traditional server/client applications, modular applications, large data handling applications and concurrent data manipulation applications to name a few. He has a BSc degree from the University of London (Homepage) and an MSc Information Security with the same university. His MSc thesis (Book) received the 2012 SearchSecurity.co.UK award (Website).

14 Responses to “Caching Made Easy with Spring”


anu
July 23, 2013 Reply

very nice article thanks

Dileep
May 22, 2014 Reply

Hi,

Very nice article, very useful and very good explanation.
I tried the example and it works for me very well.

But when I try to do caching using the ehcache , it is failing.

This is what i modified

ehcache.xml file:
———————–

<!– –>

it’s failinig says that no convertion interceptor is defined somthing like this.

please provide a solution or example with ehcache and spring .
and i am using spring-3.2.3 version.

Thanks
Dileep Donepudi

Albert Attard Albert Attard
May 23, 2014 Reply

Thank you for your comment.

Can you please send me your example at: [email protected]? I will try to see why your example failed to work.

Regards

lsri8088
July 25, 2013 Reply

Hello,
First, the tutorial is great, very well explained. And the examples I work perfectly.

Now the question ….

In my project, the cache does not work. No errors but there is always accessing database, when should cache the result of the first time.

More or less what I have as well:

@Repository(“ServerParameterDAO”)
public class ServerParameterHibernateDAO implements ServerParameterDAO {

@Cacheable(“allParameters”)
private Map getAllParameters(){
//access to data base
//return map with data
}

}

… in other file ….

@Service(“servicioServerParameter”)
public class ServicioServerParameterImpl implements ServicioServerParameter{

@Autowired
private ServerParameterDAO serverParameterDAO;

@Override
public String getParameter(String code, String defaultValue) {
return serverParameterDAO.getParameterFromCache(code, defaultValue);
}
}

… finally …

@Controller
@SessionAttributes
public class MainController {
….

@RequestMapping(value=”/tests/parameters.htm”)
public void getParameter(){
try{
logger.debug(“[TESTS] ” + servicioServerParameter.getParameter(“TEST1”, “defaultVALOR”));
}catch (Exception e) {
e.printStackTrace();
logger.debug(“[TESTS] ” + e.getMessage());
}
}

}

why doesn’t it caches ?? what is wrong ???

Thanks!!! :)

Albert Attard Albert Attard
July 25, 2013 Reply

I’m glad you like the article. Offhand I cannot tell you what’s wrong. Make sure that all three steps mentioned in the article (Search for: “In order to apply caching we need to do the following three things.”) are done especially step 2, where you enable the use of annotations. If you don’t manage to fix the problem, email me your project and will try to have a look.

Steven
November 23, 2013 Reply

I think you cannot use @Cachable on a private method (via proxy).

Albert Attard Albert Attard
November 23, 2013 Reply

I don’t believe that any of the examples shown in this page use @Cachable with a private method.

To be honest I never tried that, but I do not think it will work. A private method can only be invoked from other members from within the same outer class. AN inner class can access a private method in its outer class. I recommend you try it out and let us know how it works out.

Shiva
January 9, 2014 Reply

Thanks for this nice tutorial……

If my cache applied method is connecting to database…. i want that method should invoked when changes in database.

How can i achieve this?

Albert Attard Albert Attard
January 9, 2014 Reply

You can use a table gateway (Description) to manage all access to this table. Like that, whenever a change is made, the cache is evicted accordingly. If the table is modified outside your program, by another program of which you don’t have control, you can try the following:

  1. Add a last modified column to the table, where this column will have the timestamp when the row was last modified. The other program does not need to know about this field as it can be managed by the database.
  2. Create a thread that checks the last timestamp and if this is different to the one observed last, it will evict the cache. Do not repopulated it, as this will happen automatically when the method is next invoked.

If your table is modified very often, then you can avoid caching as you will be evicting the cache a lot.

Himanshu
February 28, 2014 Reply

Very nice explanation.

Lokesh
December 4, 2014 Reply

Very good article and nicely explained.Keep up the good work.

Albert Attard Albert Attard
December 4, 2014 Reply

I am glad you like it.

Sudha
July 24, 2015 Reply
Anil Vighne
March 30, 2016 Reply

i have created code using annotation .
//————————————————————-
package com.urs.pojo;

import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Component;

@Component
public class Worker {

@Cacheable(“task”)
public String longTask(final long id) {
System.out.printf(“Running long task for id: %d…%n”, id);
return “Long task for id ” + id + ” is done”;
}

public String shortTask(final long id) {
System.out.printf(“Running short task for id: %d…%n”, id);
return “Short task for id ” + id + ” is done”;
}
}
//————————————————————————
package com.config;

import java.util.Arrays;

import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.concurrent.ConcurrentMapCache;
import org.springframework.cache.support.SimpleCacheManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
@ComponentScan(“com.urs”)
@Configuration
@EnableCaching
public class HelloWorldConfig {
@Bean
public CacheManager cacheManager() {
// configure and return an implementation of Spring’s CacheManager SPI
SimpleCacheManager cacheManager = new SimpleCacheManager();
cacheManager.setCaches(Arrays.asList(new ConcurrentMapCache(“task”)));
return cacheManager;
}
}
//——————————————————————-
package com.urs.main;

import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.support.AbstractApplicationContext;

import com.config.HelloWorldConfig;
import com.urs.pojo.Worker;

public class Main {

public static void main(String[] args) {
// TODO Auto-generated method stub
AbstractApplicationContext ctx = new AnnotationConfigApplicationContext(HelloWorldConfig.class);
Worker w=(Worker)ctx.getBean(“worker”);
w.longTask(1);
w.longTask(1);
w.longTask(1);
w.longTask(2);
w.longTask(2);
//ctx.close();
}
}
//End

Leave a Comment


Time limit is exhausted. Please reload the CAPTCHA.