Terracotta 10.11 | Ehcache API Developer Guide | Serializers and Copiers | Copiers
 
Copiers
As the on-heap store is capable of storing plain Java objects as such, it is not necessary to rely on a serialization mechanism to copy keys and values in order to provide by value semantics. Other forms of copy mechanism can be a lot more performant, such as using a copy constructor but it requires custom code to be able to copy user classes.
Copier is the Ehcache abstraction solving this: it is specific to the on-heap store.
By default, the on-heap mappings are stored by reference. The way to store them by value is to configure copier(s) on the cache for the key, value or both.
Of course, the exact semantic of by value in this context depends heavily on the Copier implementation.
How is a copier configured?
There are two places where copiers can be configured:
*at the cache level where one can use
*CacheConfigurationBuilder.withKeyCopier(Class<? extends Copier<K>> keyCopierClass),
*CacheConfigurationBuilder.withKeyCopier(Copier<K> keyCopier),
*CacheConfigurationBuilder.withValueCopier(Class<? extends Copier<V>> valueCopierClass),
*and CacheConfigurationBuilder.withValueCopier(Copier<V> valueCopier).
which allow by instance or by class configuration.
*at the cache manager level where one can use
*CacheManagerBuilder.withCopier(Class<C> clazz, Class<? extends Copier<C>> copier)
If a copier is configured directly at the cache level, it will be used, ignoring any cache manager level configuration.
If a copier is configured at the cache manager level, upon initialization, a cache with no specifically configured copier will search through its cache manager's registered list of copiers and try to find one that directly matches the cache's key or value type. If such search fails, all the registered copiers will be tried in the added order to find one that handles compatible types.
For instance, let's say you have a Person interface and two subclasses: Employee and Customer. If you configure your cache manager as follows:
CacheManagerBuilder.newCacheManagerBuilder().withCopier(Employee.class,
EmployeeCopier.class).withCopier(Person.class,
PersonCopier.class)
then configuring a Cache<Long, Employee> would make it use the EmployeeCopier while a Cache<Long, Customer> would make it use the PersonCopier.
A Copier configured at the cache level by class will not be shared to other caches when instantiated.
Note:
Given the above, it is recommended to limit Copier registration to concrete classes and not aim for generality.
Bundled implementations
A SerializingCopier class exists in case you want to configure store by value on-heap using the configured (or default) serializer. Note that this implementation performs a serialization / deserialization on each read or write operation.
Add builder methods to section about serializing copier
The CacheConfigurationBuilder provides the following methods to make use of this specialized copier:
*CacheConfigurationBuilder.withKeySerializingCopier() for the key.
*CacheConfigurationBuilder.withValueSerializingCopier() for the value.
Lifecycle: instances vs class
When a Copier is configured by providing an instance, it is up to the provider of that instance to manage its lifecycle. It will need to dispose of any resource it used after it is no longer required.
When a Copier is configured by providing a class either at the cache or cache manager level, since Ehcache is responsible for creating the instance, it also is responsible for disposing of it. If the Copier implements java.io.Closeable then close() will be called when the cache is closed and the Copier no longer needed.
Writing your own Copier
Implement the following interface:
/**
* Defines the contract used to copy type instances.
* <p>
* The copied object's class must be preserved. The following must always be true:
* <p>
* <code>object.getClass().equals( myCopier.copyForRead(object).getClass() )</code>
* <code>object.getClass().equals( myCopier.copyForWrite(object).getClass() )</code>
* </p>
* </p>
* @param <T> the type of the instance to copy
*/
public interface Copier<T> {

/**
* Creates a copy of the instance passed in.
* <p>
* This method is invoked as a value is read from the cache.
* </p>
*
* @param obj the instance to copy
* @return the copy of the {@code obj} instance
*/
T copyForRead(T obj);

/**
* Creates a copy of the instance passed in.
* <p>
* This method is invoked as a value is written to the cache.
* </p>
*
* @param obj the instance to copy
* @return the copy of the {@code obj} instance
*/
T copyForWrite(T obj);
}
*T copyForRead(T obj) is invoked when a copy must be made upon a read operation (like a cache get()),
*T copyForWrite(T obj) is invoked when a copy must be made upon a write operation (like a cache put()).
The separation between copying for read and for write can be useful when you want to store a lighter version of your objects into the cache.
Alternatively, you can extend from org.ehcache.impl.copy.ReadWriteCopier if copying for read and copying for write implementations are identical, in which case you only have to implement:
*public abstract T copy(T obj)