Search code examples
javaspringcachingehcachespring-cache

@Cacheable Key on Shared Cache?


I have a Spring application that is using MyBatis for persistence. I am using ehcache because speed is important for this application. I've setup and configured MyBatis and Ehcache. I am using a single cache called "mybatis" because otherwisee creating a separate cache for each entity is going to be absurd.

Here is my ehcache.xml.

<?xml version="1.0" encoding="UTF-8"?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:noNamespaceSchemaLocation="ehcache.xsd"
         updateCheck="false"
         monitoring="autodetect"
         dynamicConfig="true">

    <diskStore path="java.io.tmpdir" />

    <cache name="mybatis"
           maxBytesLocalHeap="100M"
           maxBytesLocalDisk="1G"
           eternal="false"
           timeToLiveSeconds="0"
           timeToIdleSeconds="0"
           statistics="true"
           overflowToDisk="true"
           memoryStoreEvictionPolicy="LFU">
    </cache>

    <cache name="jersey"
           maxBytesLocalHeap="100M"
           maxBytesLocalDisk="1G"
           eternal="false"
           timeToLiveSeconds="600"
           timeToIdleSeconds="300"
           statistics="true"
           overflowToDisk="true"
           memoryStoreEvictionPolicy="LFU">
    </cache>

</ehcache>

Here is a sample of my mybatis mapper interface.

import java.util.List;

public interface InstitutionMapper {

    @Cacheable(value = "mybatis")
    List<Institution> getAll();

    @Cacheable(value = "mybatis", key = "id")
    Institution getById(long id);

    @CacheEvict(value = "mybatis")
    void save(Institution institution);

    @CacheEvict(value = "mybatis", key = "id")
    void delete(long id);
}

Because I have a shared cache I need a way for my keys to be unique to a domain object. As an example on save or delete I need to clear the cache so the new values will show up on the UI. However I don't want to clear the entire cache. I don't know how to approach this so that when delete gets called and the cache is evicted, only entries in the mybatis cache with the for Institution with that ID gets cleared.

The key would need to be something like domain name + params. As an example institution + id. Hopefully that makes sense.

I saw this post but it seems to be going by class name + method + params.


Solution

  • Having a single region for your whole domain model is a bit weird (to say the least). I can imagine that you could gather object types with similar semantics in the same cache but not all object types. Most of the questions that you are asking here would solve themselves if you had a proper demarcation.

    But for the sake of the explanation, here are some thoughts.

    Your getAll() needs an key. If you don't provide one, then basically any other @Cacheable method with no argument would conflict with the same key in the cache.

    @Cacheable(value = 'mybatis', key = "'institutions'")
    List<Institution> getAll();
    

    Your @CacheEvict is not going to clear the cached list (from the getAll() method) so you could be in a situation where you evict an institution and it still shows up from the cached getAll() call. If you want to cache the same thing at several levels you better of drop the entire region when you update/delete something. This is of course less of a problem if you have a region per entity type.

    Your save method has no id. What is it supposed to evict exactly? How does it know it has to find an existing institution by id?

    @CacheEvict(value = "mybatis", key = "#p0.id")
    void save(Institution institution);
    

    (that won't solve the getAll() inconsistency though)

    Your getById does not require a key since the only method argument you have there is the id. Back to your original "problem", if you want to prefix your keys with something, you need to do it across the board (so that eviction works against the same key). I wouldn't do that in SpEL as the chance to forget one case is just too high.

    You could implement a custom KeyResolver and append a unique prefix according to the return type of the method.

    That being said, your sample code is close from being all wrong so I'd advise you to review the documentation on this topic