(Quick Reference)

21 Grails Cache

Version: 7.0.0-SNAPSHOT

21 Grails Cache

The Grails Cache plugin provides powerful and easy to use caching functionality to Grails applications and plugins.

21.1 Usage

The cache plugin adds Spring bean method call, GSP page fragment and template caching to Grails applications. You configure one or more caches in grails-app/conf/application.yml, and annotate methods (either in Spring beans (typically Grails services)) to be cached. You can also wrap GSP sections in cache tags and render cached templates.

There are three annotations; Cacheable, CachePut, and CacheEvict. You use Cacheable to mark a method as one that should check the cache for a pre-existing result, or generate a new result and cache it. Use CachePut to mark a method as one that should always be evaluated and store its result in the cache regardless of existing cache values. And use @CacheEvict to flush a cache (either fully or partially) to force the re-evaluation of previously cached results.

When using distributed caching (such as ehcache with distributed cache enabled, or redis with multiple instances of the application running against one redis instance), all classes that use annotation caching or XML caching should override the hashCode method. The hash code of the object with the method marked as being cacheable is included in the cache key, and the default hashCode implementation will vary each time the application is run. Overriding hashCode ensures that each instance of the applications will appropriately share cache keys.

This 'core' cache plugin uses an in-memory implementation where the caches and cache manager are backed by a thread-safe java.util.concurrent.ConcurrentMap. This is fine for testing and possibly for low-traffic sites, but you should consider using one of the extension plugins if you need clustering, disk storage, persistence between restarts, and more configurability of features like time-to-live, maximum cache size, etc. Currently the only extension plugin available for Grails 3 is cache-ehcache.

21.1.1 Configuration

Configuration Options

There are a few configuration options for the plugin; these are specified in grails-app/conf/application.yml.

Property Default Description

grails.cache.enabled

true

Whether to enable the plugin

grails.cache.clearAtStartup

false

Whether to clear all caches at startup

grails.cache.cacheManager

lGrailsConcurrentMapCacheManager

Cache Manager to use. Default cache manager uses Spring Frameworks ConcurrentMapCache which might grow limitless. If you cannot predict how many cache entries you are going to generate use "GrailsConcurrentLinkedMapCacheManager" instead which uses com.googlecode.concurrentlinkedhashmap.ConcurrentLinkedHashMap and limits by default to 10000 entries per cache.

The cache implementation used by this plugin is very simple, so there aren’t many configuration options (compared to the Ehcache implementation for example, where you have fine-grained control over features like overflowing to disk, time-to-live settings, maximum size of caches, etc.) So there aren’t many supported options in the cache configuration.

Since there is no way to configure "time to live" with this plugin, all cached items have no timeout and remain cached until either the JVM restarts (since the backing store is in-memory) or the cache is partially or fully cleared (by calling a method or action annotated with \@CacheEvict or programmatically).

If you don’t need to supply any configuration to your cache, simply don’t include it in the configuration.

If you want to limit the number of cache entries you have to change the default cache manager to 'GrailsConcurrentLinkedMapCacheManager'. To specify the limit you can add the maxCapacity parameter to the cache config. Default value for maxCapacity is 10000.

grails:
    cache:
        cacheManager: GrailsConcurrentLinkedMapCacheManager
        caches:
            message:
                maxCapacity: 5000
            maps:
                maxCapacity: 6000

21.1.2 Cache Annotations

The Cacheable and CacheEvict annotations provided by the plugin have counterparts with the same names provided by Spring.

However, unlike the Spring versions, these apply AST transformations, avoiding the need to create runtime proxies.

Service Method Caching

Given the below simple service, you can see that the getMessage method is configured to cache the results in the "message" cache.

package com.yourcompany

import grails.plugin.cache.*

class MessageService {

   @Cacheable('message')
   Message getMessage(String title) {
      println 'Fetching message'
      Message.findByTitle(title)
   }
}

By default the cache key is calculated using:

  • The class name

  • The method name

  • The parameter names and values

In the previous example, the title parameter will be used as the cache key; if there were multiple parameters they would be combined into the key.

You can use a closure to dynamically define the key. For example:

package com.yourcompany

import grails.plugin.cache.*

class MessageService {

   @Cacheable(value = 'message', key = { title.toUpperCase() } )
   Message getMessage(String title) {
      println 'Fetching message'
      Message.findByTitle(title)
   }
}

The CachePut annotation can be used to place a value into the cache. For example:

package com.yourcompany

import grails.plugin.cache.*

class MessageService {

   ...

   @CachePut(value='message', key = { message.title })
   void save(Message message) {
      println "Saving message $message"
      message.save()
   }
}

The save method in the example above is configured as one that evicts elements from the cache. There is no need to clear the entire cache in this case; instead any previously cached item with the same title attribute will be replaced with the current Message instance.

Note that you could also use CacheEvict for the save method, which would remove the old cached value but not cache the current value:

package com.yourcompany

import grails.plugin.cache.*

class MessageService {

   ...

   @CacheEvict(value='message', key = { message.title })
   void delete(Message message) {
      println "Deleting message $message"
      message.delete()
   }
}

This service works with the Message domain class:

package com.yourcompany

class Message implements Serializable {

   private static final long serialVersionUID = 1

   String title
   String body

   String toString() {
      "$title: $body"
   }
}

Note that for in-memory cache implementations it’s not required that the objects being cached implement Serializable but if you use an implementation that uses Java serialization (for example the Redis plugin, or the Ehcache plugin when you have configured clustered caching) you must implement Serializable.

To test this out, be sure to define a "message" cache in grails-app/conf/application.yml and save and retrieve Message instances using the service. There are println statements but you can also turn on SQL logging to watch the database access that’s needed to retrieve instances that aren’t cached yet, and you shouldn’t see database access for cached values.

21.1.2.1 Unit Testing

In general, code that is marked with cache related annotations can be unit tested without doing anything special to deal with caching. The caching just won’t be enabled and the code in cached methods will be executed each time the method is invoked.

@CompileStatic
class BasicCachingService {

    private int invocationCounter = 0
    private int conditionalInvocationCounter = 0

    def getInvocationCounter() {
        invocationCounter
    }

    @Cacheable('basic')
    def getData() {
        ++invocationCounter
        'Hello World!'
    }
}
/*
 *  Licensed to the Apache Software Foundation (ASF) under one
 *  or more contributor license agreements.  See the NOTICE file
 *  distributed with this work for additional information
 *  regarding copyright ownership.  The ASF licenses this file
 *  to you under the Apache License, Version 2.0 (the
 *  "License"); you may not use this file except in compliance
 *  with the License.  You may obtain a copy of the License at
 *
 *    https://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing,
 *  software distributed under the License is distributed on an
 *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 *  KIND, either express or implied.  See the License for the
 *  specific language governing permissions and limitations
 *  under the License.
 */

package com.demo

import grails.testing.services.ServiceUnitTest
import spock.lang.Specification

class BasicCachingServiceNoCacheManagerSpec extends Specification implements ServiceUnitTest<BasicCachingService> {

    void 'test invoking cacheable method when no cache manager is present'() {
        when: 'a cached method is invoked the first time'
        def result = service.getData()

        then: 'the code in the method is executed'
        result == 'Hello World!'
        service.invocationCounter == 1

        when: 'a cached method is invoked the second time'
        result = service.getData()

        then: 'the code in the method is still executed because no cache manager is present'
        result == 'Hello World!'
        service.invocationCounter == 2
    }
}

In order for caching to be active when the unit test is running, cache manager and key generator beans must be added to the Spring application context by the test.

/*
 *  Licensed to the Apache Software Foundation (ASF) under one
 *  or more contributor license agreements.  See the NOTICE file
 *  distributed with this work for additional information
 *  regarding copyright ownership.  The ASF licenses this file
 *  to you under the Apache License, Version 2.0 (the
 *  "License"); you may not use this file except in compliance
 *  with the License.  You may obtain a copy of the License at
 *
 *    https://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing,
 *  software distributed under the License is distributed on an
 *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 *  KIND, either express or implied.  See the License for the
 *  specific language governing permissions and limitations
 *  under the License.
 */

package com.demo

import grails.plugin.cache.CustomCacheKeyGenerator
import grails.plugin.cache.GrailsConcurrentMapCacheManager
import grails.testing.services.ServiceUnitTest
import spock.lang.Specification

class BasicCachingServiceSpec extends Specification implements ServiceUnitTest<BasicCachingService> {

    @Override
    Closure doWithSpring() {{ ->
        grailsCacheManager(GrailsConcurrentMapCacheManager)
        customCacheKeyGenerator(CustomCacheKeyGenerator)
    }}

    void 'test invoking cacheable method when cache manager is present'() {
        when: 'a cached method is invoked the first time'
        def result = service.getData()

        then: 'the code in the method is executed'
        result == 'Hello World!'
        service.invocationCounter == 1

        when: 'a cached method is invoked the second time'
        result = service.getData()

        then: 'the cached return value is returned and the code in the method is not executed'
        result == 'Hello World!'
        service.invocationCounter == 1
    }

    void 'test invoking a cacheable method that expresses a condition'() {
        when: 'multiply is called with x < 10'
        def result = service.multiply(4, 7)

        then: 'the method should have been invoked'
        result == 28
        service.conditionalInvocationCounter == 1

        when: 'the method is invoked with x > 10'
        result = service.multiply(40, 7)

        then: 'the method should have executed'
        result == 280
        service.conditionalInvocationCounter == 2

        when: 'multiply is called with x < 10 with a cached value'
        result = service.multiply(4, 7)

        then: 'the method should not have executed'
        result == 28
        service.conditionalInvocationCounter == 2

        when: 'the method is invoked with x > 10 again'
        result = service.multiply(40, 7)

        then: 'the condition should prevent caching'
        result == 280
        service.conditionalInvocationCounter == 3
    }
}

21.1.3 The CacheManager

The plugin registers an instance of the CacheManager interface as the grailsCacheManager Spring bean, so it’s easy to access using dependency injection.

The most common method you would call on the grailsCacheManager is getCache(String name) to access a Cache instance programmatically. This shouldn’t be needed often however. From the Cache instance you can also access the underlying cache implementation using cache.getNativeCache().

21.2 Caching Tags

The plugin provides GSP tags which are useful for caching the result of evaluating sections of markup. These tags allow for the result of evaluating sections of markup to be cached so subsequent renderings of the same markup do not have to result in the markup being evaluated again. These tags support automatic cache eviction with the use of a time to live (ttl) attribute.

See the documentation for the block and render tags for more details.

21.3 The Cache Admin Service

The plugin provides a service named GrailsCacheAdminService which support various methods for administering caches.

21.3.1 Clearing Caches

There are methods in GrailsCacheAdminService for clearing the caches used by the block and render tags.

class ReportingController {

    def grailsCacheAdminService

    def report() {
        // clear the cache used by the blocks tag...
        grailsCacheAdminService.clearBlocksCache()

        // clear the cache used by the render tag...
        grailsCacheAdminService.clearTemplatesCache()

        ...
    }
}

21.4 Implementation Details

All of the plugin’s classes are designed for extensibility; the classes are all public, and fields and methods are mostly public or protected. Consider subclassing existing classes to reuse as much as possible instead of completely rewriting them.

Cache manager

The core cache plugin registers a grailsCacheManager Spring bean, and the extension plugins replace this bean with one that creates and manages caches for that implementation. The default implementation is an instance of GrailsConcurrentMapCacheManager which uses GrailsConcurrentMapCache as its cache implementation. It uses a java.util.concurrent.ConcurrentHashMap to store cached values.

You can customize the cache manager by replacing the grailsCacheManager Spring bean in resources.groovy with your own; either subclass GrailsConcurrentMapCacheManager (e.g. to override the createConcurrentMapCache() method) or by implementing the GrailsCacheManager interface.

Fragment caching

You can cache partial GSP page sections with the <cache:block> tag. You can specify a key when using this tag but it’s in general unnecessary. This is because the block will be rendered with its own Closure, and the default key is the full closure class name. This is unique since the closures aren’t re-used; for example these two blocks will be cached independently, even in the same GSP:

<cache:block>
foo
</cache:block>

<cache:block>
bar
</cache:block>

You can cache the content of templates with the <cache:render> tag. You can specify a key when using this tag but like the block tag, it’s in general unnecessary because the default key is the full template class name.

Service caching

You can cache the return value of a service method by annotating it with Cacheable.

Key generation

The default key generated implements the GrailsCacheKeyGenerator which provides method for generating keys taking into account:

  • The class name

  • The method name

  • A hashCode for the current object

  • The parameters or if a closure is specified to generate the key the value of the closure call

You can override this implementation by defining a bean called customCacheKeyGenerator via Spring.