grails:
cache:
cacheManager: GrailsConcurrentLinkedMapCacheManager
caches:
message:
maxCapacity: 5000
maps:
maxCapacity: 6000
21 Grails Cache
Version: 7.0.0-SNAPSHOT
Table of Contents
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 |
|
Whether to enable the plugin |
grails.cache.clearAtStartup |
|
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.
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.
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.