3. Caching Service Methods

The typical use case for method caching is when you have Grails service methods that invoke expensive operations such as HTTP gets, web service calls, filesystem IO, etc.

Although you can use the Springcache plugin to cache service methods that query or update GORM domain objects you should consider whether it's more appropriate to use the Hibernate 2nd level cache (see the relevant sections in the Grails documentation). In some cases using Springcache does make sense, e.g. a service that aggregates the results of multiple queries.

Simply add an @Cacheable annotation to methods that should cache their results and a @CacheFlush annotation to methods that should flush caches.

Be aware that the annotations will only have any effect on Spring-managed beans. If you create instances of your class directly rather than getting them from the application context they will not be decorated with caching/flushing behaviour.

Method caching requires AspectJ auto-weaving to be enabled. If you have grails.spring.disable.aspectj.autoweaving = false set in config then method caching will not work. Content caching is unaffected as it uses a different mechanism.

A simple example might be:

PiracyService.groovy

@Cacheable("pirateCache")
def getPirates() {
    // return a list of pirates
}

@Cacheable("pirateCache") def findPirates(name) { // return a particular pirate }

@Cacheable("shipCache") def getShips() { // return a list of ships }

@CacheFlush("pirateCache") void registerNewPirate(Pirate sailor) { // store a new pirate }

@CacheFlush("shipCache") void registerNewShip(Ship ship) { // store a new ship }

@CacheFlush(["pirateCache", "shipCache"]) void registerNewShipWithCrew(Ship ship, Collection<Sailor> crew) { // store a new ship and associated pirates }

This ties the flushes on the register methods to the particular caches they affect, so after calling registerNewPirate the methods getPirates and findPirates will re-populate their cached results but getShips would still use any cached results from previous calls. Calling registerNewShipWithCrew will flush both caches.

It is fine for multiple methods to share the same caches. Both getPirates and findPirates in the example above share the same cache. Cache entries are keyed on target object (the service instance in this case), method name and call parameters so there should be no confusion when using the same caches on multiple methods.

There are various strategies you can adopt in naming and grouping caches, this example shouldn't be seen as definitive.

3.1. Service Method Cache Keys

When a @Cacheable annotation is found on a service method the plugin generates a key using:

Since Grails services are typically Spring singletons the target object is not usually an issue. There's no need to implements equals or hashCode on your service classes unless you are using a different Spring bean scope and need to differentiate between calls made to different instances of the service.

It is, however, vital to ensure that equals and hashCode is properly implemented on all the types used as parameters to cached methods. If this is not done it is very unlikely that the cache will ever be hit.

3.2 Calling Cached Methods Internally

Service method caching is implemented via Spring AOP, which utilises proxies. In practical terms, this means that when depending on a service with a cached method in another service or controller (or anything else for that matter), you actual receive a proxy for the real service. This allows method calls to be intercepted and for caches to be checked or populated.

The implication of this however is that calls to this (implicit or explicit) do NOT go via the proxy.

Consider the following…

class ExampleService {

def nonCachedMethod() { cachedMethod() }

@Cacheable('cachedMethodCache') def cachedMethod() { // do some expensive stuff } }

You may expect that the nonCachedMethod() will use the cache for cachedMethod(), but it won't. The call is made on this which is the actual instance and not the proxy.

Fortunately, there is an easy workaround…

class ExampleService {

def grailsApplication

def nonCachedMethod() { grailsApplication.mainContext.exampleService.cachedMethod() }

@Cacheable('cachedMethodCache') def cachedMethod() { // do some expensive stuff } }

Instead of calling the method on this, we obtain the proxy via the application context (i.e. grailsApplication.mainContext.exampleService) and call the method on that. This way we go through the caching mechanism.