Spring Cache - Part 1 - Introduction


Caching is an extremely important aspect of applications that care about lower latencies. There are a multitude of rules one has to adhere to while setting up a cache, in order to optimize the performance; but not overdo it. We will not get into those details in this post. Our focus would be on what Spring provides to enable caching in your applications.

Spring cache was introduced in Spring 3.1. It provides a nice little framework for enabling cache on data access components declaratively. Since then it has gone through several updates and now it supports JSR-107 (a.k.a JCache), which is a specification for in-memory caching.

Let's examine through a bunch of examples, how we can leverage Spring cache.

Dependencies


All cache specific spring components are part of the spring-context module. Add the following maven dependency to the pom.xml to pull in spring cache components.
<dependency>
   <groupid>org.springframework</groupid>
   <artifactid>spring-context</artifactid>
   <version>4.3.6.RELEASE</version>
</dependency>

Sample data retrieval service


The following set of classes are sample classes that help understand the usage of Spring cache. We would use the movie actor database as an example here.

ActorService is a service class that provides methods for retrieving the movie actor details. Actor is the POJO that holds the actor information.

@Component
public class ActorService {

    public List<actor> getActors() {
        System.out.println("----- Getting movie actors ----");
        List<actor> actors = new ArrayList<actor>();
        actors.add(new Actor("Sean Connery", Gender.MALE));
        actors.add(new Actor("Meryl Streep", Gender.FEMALE));
        return actors;
    }
}

ApplicationConfig - is the java configuration class for the beans. We will add bean declarations here as we go forward. But currently, it is empty.
@Configuration
public class ApplicationConfig {

}

And the following is the code snippet that initializes and loads the spring application context and runs the tests. This routine initializes the AnnotationConfigApplicationContext, that scans for all components defined under "com.codelooru.cache" package and sub-packages. Then it fetches the ActorService from the application context and invokes the getActors() twice on the service. Our goal would be to ensure that the second call does not invoke the method, but rather fetches the data from the cache.

AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext("com.codelooru.cache"); 
ActorService service = context.getBean("actorService", ActorService.class); 
service.getActors();// called once. 
service.getActors();// called twice. 
context.close(); 

Without the cache enabled, when this code is run, we would see the following output on the console.
----- Getting movie actors ----
----- Getting movie actors ----

Enabling cache with Spring


@EnableCaching and CacheManager


To enable cache, we need to first annotate the ApplicationConfig with @EnableCaching and configure a CacheManager bean.

@EnableCaching
@Configuration
public class ApplicationConfig {
    @Bean
    public CacheManager cacheManager() {
        SimpleCacheManager cacheManager = new SimpleCacheManager();
        Cache actorsCache = new ConcurrentMapCache("actors");
        cacheManager.setCaches(Arrays.asList(actorsCache));
        return cacheManager;
    }
}

@EnableCaching registers and configures all the necessary proxies and interceptors around the cacheable components. These interceptors intercept the method requests and invoke the CacheManagers to fetch the data. This annotation should always be accompanied by a bean definition for the CacheManager.

CacheManager is responsible for managing the cacheable data. It knows how to store and retrieve the data. Spring provides a few CacheManagers out of the box, but these should not be used for enterprise level applications. They are more suited for tests or simple tools and applications. For enterprise applications, there are better 3rd party components like Ehcache.

Cache


CacheManagers can manage several Caches. A Cache usually has a name and is used for grouping together multiple cacheable data items. It is akin to a map that returns back a value for the given key. Each cache can be configured with its own eviction policies. More about eviction would be covered in later parts of the article. 

Example with cache enabled


In our example, we will use SimpleCacheManager, which is a very basic implementation of cache management with hashmaps. We initialize it with a cache for "actors". The next step is to mark the getActors method as cacheable.

@Component
public class ActorService {

    @Cacheable("actors")
    public List<actor> getActors() {
        System.out.println("----- Getting movie actors ----");
        List<actor> actors = new ArrayList<actor>();
        actors.add(new Actor("Sean Connery", Gender.MALE));
        actors.add(new Actor("Meryl Streep", Gender.FEMALE));
        return actors;
    }
}

@Cacheable annotation is used to mark a Class or method as cacheable. It takes the cache name as the default value.  When the getActors() method is first invoked, it would check the "actors" cache for the data and when it does not find it, it would invoke the actual method to get the data. The result is then stored in the cache.

With the cache enabled, now the result of the test program would be as below. This means the actual getActors method is invoked only once and the second time, the data is fetched from the cache.

----- Getting movie actors ----

In Part 2, we will explore cache keys.