Magnolia 5.4 reached end of life on November 15, 2018. This branch is no longer supported, see End-of-life policy.
This page explains how to cache arbitrary objects in Magnolia. You can cache basically every object. This takes caching well beyond page and page fragment caching.
The functionality for caching arbitrary objects is provided by at least two modules.
magnolia-cache-core
which defines the cache API.
magnolia-cache-ehcache
is the default implementation which uses Ehcache.
magnolia-cache-memcached
is a high-performance, distributed memory object caching system which uses Memcached. The main reason to cache objects is to serve data faster and to save resources (cpu, memory, network load, etc.). For instance, use object caching when there is heavy request on a Magnolia "service" which must provide data from a remote source.
When delivering cached data you will probably need a mechanism to ensure cached data is valid and not outdated! How to handle this depends on the use case.
When putting objects to the cache you can restrict their lifetime. In this case, instead of checking the validity of cached objects, you can simply let them expire over time.
Whether cache is persistent depends on the underlying implementation. For non-persistent implementations the cache must be rebuilt after server restart. See Cache implementations for more.
Implementations:
Not persistent | EhCache with free license |
Persistent |
Achieve an instance of Cache . This is your cache to operate on. Give it a name which must be unique within the system.
Example:
public class MyClass { private final Provider<CacheFactoryProvider> cacheFactoryProvider; @Inject public MyClass(Provider<CacheFactoryProvider> cacheFactoryProvider){ this.cacheFactoryProvider = cacheFactoryProvider; } public void fooBar(){ Cache cache = cacheFactoryProvider.get().get().getCache("fooBarCache"); } }
All subsequent actions - adding, reading and removing objects - are applied on this Cache
object.
Magnolia allows to configure a cache factory specifically for each cache. For example you could have a specific configuration for fooBarCache
. When no specific configuration is defined, the default
factory configuration is used.
Example: Extend the default
factory and override the persistence strategy with none
. This persistence strategy allows to cache non-serializable objects.
Node name | Value |
---|---|
modules | |
cache | |
config | |
cacheFactory | |
caches | |
default | |
fooBarCache | |
extends | ../default |
timeToLiveSeconds | 300 |
persistence | |
strategy | none |
The
/modules/cache/config/cacheFactory/caches/fooBarCache
node defines the cache name. fooBarCache
is the "name" of the cache which was also used above when acquiring the Cache object within Java code.
public interface Cache { boolean hasElement(Object key); void put(Object key, Object value); void put(Object key, Object value, int timeToLiveInSeconds); Object get(Object key); Object getQuiet(Object key); void remove(Object key); void clear(); String getName(); int getSize(); Collection<Object> getKeys(); }
Cache knows two methods to append objects.
void put(Object key, Object value); void put(Object key, Object value, int timeToLiveInSeconds);
Arguments:
key | required The identifier of the object to cache |
value | required The object to cache |
timeToLiveInSeconds | optional The lifetime of the cached object in seconds. Tip: The lifetime also can be configured globally, and you can configure a flush policy to clear the cache when some condition is met, or you may want to delete cached objects programmatically (see #remove and #clear below). |
Example:
public class MyClass { public Object put(Object key, Object value){ Cache cache = cacheFactoryProvider.get().get().getCache("fooBarCache"); cache.put(key, value); return value; } public Object put(Object key, Object value, int timeToLiveInSeconds){ Cache cache = cacheFactoryProvider.get().get().getCache("fooBarCache"); cache.put(key, value, timeToLiveInSeconds); return value; } }
There are two methods to retrieve an object from cache.
Object get(Object key); Object getQuiet(Object key);
Returns an object from the cache and blocks all the requests for the same key.
Example:
public JsonNode getData(String productId) throws Exception { Cache cache = cacheFactoryProvider.get().get().getCache(CACHEKEY); try { JsonNode jsonNode = (JsonNode) cache.get(productId); if (jsonNode != null) { return jsonNode; } else { jsonNode = readJson(productId); cache.put(productId, jsonNode); return jsonNode; } } catch (Exception e) { cache.put(productId, null); //unblock cache! throw e; } }
Use #get
when you are unsure whether the desired object is already cached. If the object is not yet cached, you should acquire it by other means and add it to the cache.
It is good practice to call #get
and #put
within the same associated try-catch-finally block for the same cache key.
If there is no object for a key, add null
to the cache anyway. This way you can ensure that the service doesn't try to read data for a key without an existing value more than one time
Returns an object from the cache but does NOT block requests for the same key.
Use #getQuiet
to acquire objects which are already cached.
This method is used more to "observe" or manage cached objects than to fetch a specific object. The Magnolia Cache Browser app is using #getQuiet, for instance.
Example:
public List getCachedObjects() { List<Object> cachedObjects = new ArrayList<>(); Cache cache = cacheFactoryProvider.get().get().getCache(NAME_OF_CACHE); Collection<Object> keys = cache.getKeys(); for (Object key : keys) { Object value = cache.getQuiet(key); if (value != null) { cachedObjects.add(value); } } return cachedObjects; }
Line 8: Uses #getQuiet
Cache cache = cacheFactoryProvider.get().get().getCache("fooBarCache"); cache.remove("fb-90270240-xyz");
#clear
deletes all items of a specific
Cache
which is identified by its "name"
cacheFactoryProvider.get().get().getCache("fooBarCache").clear()