grails:
cache:
cacheManager: GrailsConcurrentLinkedMapCacheManager
caches:
message:
maxCapacity: 5000
maps:
maxCapacity: 6000
3 Usage
Version: 6.3.0
3 Usage
The cache plugin adds Spring bean method call, GSP page fragment and template caching to Grace applications. You configure one or more caches in app/conf/application.yml
, and annotate methods (either in Spring beans (typically Grace 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.
3.1 Configuration
Configuration Options
There are a few configuration options for the plugin; these are specified in 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.
3.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 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.
3.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!'
}
}
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.
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
}
}
3.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()
.