Spring Cache Abstraction

Caches have been used for a very long time. To temporarily store data, to improve performance...

Since Spring 3.1 we're able to remove a lot of that boiler plate code due to the implementation of a Cache Abstraction. What it basically means is that we can annotate methods to be able to automatically cache the return values. This means that we can technically store data in a cache, by simply adding an annotation and providing the cache implementation at startup.

For this simple example, I'll show you how can you make use of a simple map-cache to store return values of methods. I'll be using spring boot for this example, but the code I'll be showing below doesn't depend on it.

The examples for this article can be found on my Github account

A series of articles

This is the first part of a series of articles I'll be writing about some useful technologies, features of technologies and code snippets. All articles will be accompanied by a Github Repository. When newer versions of the repositories come online, new articles will be written to explain the features.

The Configuration File

For almost all of my spring-based projects, I tend to use a javaconfig-based approach. I really dislike xml-files. They're hard to read, hard to maintain and harder to refactor (but of course, that's my personal opinion) and when it comes to spring boot, using configuration files couldn't be any easier.

@Configuration
@EnableCaching
public class SimpleCacheConfiguration {

  @Bean
  public SimpleCacheManager cacheManager() {
      SimpleCacheManager cacheManager = new SimpleCacheManager();
      List<Cache> caches = new ArrayList<>();
      caches.add(messagesCache().getObject());
      cacheManager.setCaches(caches );
      return cacheManager;
  }

  @Bean
  public ConcurrentMapCacheFactoryBean messagesCache(){
      ConcurrentMapCacheFactoryBean cacheFactoryBean = new ConcurrentMapCacheFactoryBean();
      cacheFactoryBean.setName("messagesCache");
      return cacheFactoryBean;
  }

}

Basically, what this configuration file does is is provide a SimpleCacheManager for spring to use. We then simply add an instance of a ConcurrentMapCacheFactoryBean, which will produce a map-based cache.

As you can see, the class is annotated with configuration, meaning it's a configuration class and will be treated that way. @EnableCaching will simply enable the cache abstraction.

The interface

public interface IMessageService {
    CacheableMessage getById(Long id);
}

There's not much we can tell about this interface. It's the contract for our service. It expects an id [Long] and will return a message [CacheableMessage].

Our Service Implementation

This is where the cache implementation will become clear. We'll be implementing the interface, calling an external source to get our data. In our case, ICachingRepository is the class that fetches the actual data. For the sake of the article, imagine it being an expensive call to a webservice.

@Service
public class CacheableMessageService implements IMessageService {

    private ICachingRepository cachingRepository;

    @Autowired
    public CacheableMessageService(ICachingRepository cachingRepository) {
        this.cachingRepository = cachingRepository;
    }

    @Cacheable(value = "messagesCache")
    @Override
    public CacheableMessage getById(Long id) {
        return cachingRepository.getById(id);
    }
}

The only thing that's special about this class is the @Cacheable annotation. Spring will intercept any call to this method and check the messagesCache - which we defined in the configuration-file - for existing entries with the id as key. If the key is not found, the actual method will be executed. If it was found however, spring will return the value it found corresponding to the key.

Coming up

This was just a small example for you to understand the Spring Cache Abstraction.

In one of my next articles, I'll dig a little deeper into some code to show you how you can evict caches and construct more complex keys- compared to just the id.