javacachingehcacheehcache-bigmemoryjsr107

EhCache cached object modification


I've to implement caching with EhCache. Basic requirement is, I have to keep that cached object for fixed interval ( for now 1 hours in code below). So, I implemented the code as below:

Sample domain object:

import lombok.*;
@Getter
@Setter
@ToString
@AllArgsConstructor
public class City implements Serializable {
    public String name;
    public String country;
    public int population;
}

Cache manager class:

import net.sf.ehcache.*;
public class JsonObjCacheManager {
    private static final Logger logger = LoggerFactory.getLogger(JsonObjCacheManager.class);
    private CacheManager manager;

    private Cache objectCache;

    public JsonObjCacheManager(){
        manager = CacheManager.create();

        objectCache =  manager.getCache("jsonDocCache");

        if( objectCache == null){
            objectCache = new Cache(
                    new CacheConfiguration("jsonDocCache", 1000)
                            .memoryStoreEvictionPolicy(MemoryStoreEvictionPolicy.LRU)
                            .eternal(false)
                            .timeToLiveSeconds(60 * 60)
                            .timeToIdleSeconds(0)
                            .diskExpiryThreadIntervalSeconds(0)
                            .persistence(new PersistenceConfiguration().strategy(PersistenceConfiguration.Strategy.LOCALTEMPSWAP)));
            objectCache.disableDynamicFeatures();
            manager.addCache(objectCache);
        }
    }

    public List<String> getKeys() { return objectCache.getKeys();}

    public void clearCache(){
        manager.removeAllCaches();
    }

    public void putInCache(String key, Object value){
        try{
            objectCache.put(new Element(key, value));
        }catch (CacheException e){
            logger.error(String.format( "Problem occurred while putting data into cache: %s", e.getMessage()));
        }
    }

    public Object retrieveFromCache(String key){
        try {
            Element element = objectCache.get(key);
            if(element != null)
                return element.getObjectValue();
        }catch (CacheException ce){
            logger.error(String.format("Problem occurred while trying to retrieveSpecific from cache: %s", ce.getMessage()));
        }
        return null;
    }
}

It caches and retrieves the values very properly. But my requirement is, I must modify the object that I retrieve from cache for given key. What I'm getting is, if I modify the object that I retrieved from cache, then cached object for that key is also getting modified.

Below is the example:

    public class Application {

        public static void main(String[] args) {
            JsonObjCacheManager manager = new JsonObjCacheManager();

            final City city1 = new City("ATL","USA",12100);
            final City city2 = new City("FL","USA",12000);

            manager.putInCache(city1.getName(), city1);
            manager.putInCache(city2.getName(), city2);

            System.out.println(manager.getKeys());

            for(String key: manager.getKeys()){
                System.out.println(key + ": "+ manager.retrieveFromCache(key));
            }

            City cityFromCache = (City) manager.retrieveFromCache(city1.getName());
            cityFromCache.setName("KTM");
            cityFromCache.setCountry("NPL");
            System.out.println(manager.getKeys());

            for(String key: manager.getKeys()){
                System.out.println(key  + ": "+ manager.retrieveFromCache(key));
            }
        } 
}

The output that I'm getting is:

[ATL, FL]
ATL: City(name=ATL, country=USA, population=12100)
FL: City(name=FL, country=USA, population=12000)
[ATL, FL]
ATL: City(name=KTM, country=NPL, population=12100)
FL: City(name=FL, country=USA, population=12000)

This means, whenever I'm retrieving and modifying the object for given key, it also being reflected in cached value.

What my requirement is, the cached object for given key should not be modified. Is there any way to achieve this? Or is it not correct way to implement EhCache? Or I'm missing some fundamental principle?

I'm using EhCache V2.10.3

Thank you!


Solution

  • When you use a cache that is storing its data on the heap and with direct object references, you need to copy the object before using it.

    In general it is good practice not to mutate a value after handing over the object reference to the cache (or anybody else beyond your control).

    Some caches do have a copy mechanism to protect the cached values from modification. E.g. in EHCache3 you can add copiers, see Serializers and Copiers.

    Alternatively, change your design: When you have the need to mutate the value, maybe you can split the values into two objects, one that is caches, one that contains the data that needs mutating and make the latter containing the first.