6

I'm using Spring Boot and EhCache to develop a calendar application. I'm trying to cache the following method:

@Override
@Cacheable(value = "concerts")
public List<Event> getEvents(String eventsForUser, Date startDate, Date endDate) throws Exception {
    return fetchEventsFromTheServer(eventsForUser, startDate, endDate);
}

The challenge is I would like to manipulate returned cached result. For example, check if there is cache for given dates but for a different user and then return it instead (as long as both users meet certain criteria).

So, before returning a result I would like:

  • to get a list of all cached entries
  • loop through all of them and check for needed dates/users
  • if found suitable - return that
  • if not found - cache is not available, run the method.

I think the best would be to create a custom Cache Manager which will do all the manipulation with cached concert and use default auto configured cache for all other methods, but I can't get my custom manager to work and didn't find any good example on how to implement a custom CacheManager.

Here is what I have:

Application.java:

@SpringBootApplication
@ComponentScan
@EnableCaching
@EnableScheduling
public class SpringBootWebApplication extends SpringBootServletInitializer {

    public static void main(String[] args) {
        SpringApplication.run(SpringBootWebApplication.class, args);
    }

    @Override
    protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
        return application.sources(applicationClass);
    }

    private static Class<SpringBootWebApplication> applicationClass = SpringBootWebApplication.class;


    @Bean(name = "eventsCacheManager")
    public EventsCacheManager eventsCacheManager() {

        return new EventsCacheManager();
    }

    @Primary
    @Bean(name = "cacheManager")
    public CacheManager cacheManager() {
        return new EhCacheCacheManager(ehCacheCacheManager().getObject());
    }

    @Bean
    public EhCacheManagerFactoryBean ehCacheCacheManager() {
        EhCacheManagerFactoryBean cmfb = new EhCacheManagerFactoryBean();
        cmfb.setConfigLocation(new ClassPathResource("ehcache.xml"));
        cmfb.setShared(true);
        return cmfb;
    }


}

EventsCacheManager.java

@Component
public class EventsCacheManager implements CacheManager  {

    @Override
    public Cache getCache(String s) {
        return null;
    }

    @Override
    public Collection<String> getCacheNames() {
        return null;
    }
}

EventsCacheManager.java - how to implement it?

@Component
public class EventsCacheManager implements CacheManager  {

    @Override
    public Cache getCache(String s) {
        return null;
    }

    @Override
    public Collection<String> getCacheNames() {
        return null;
    }
}

I would really appreciate if someone can give me an example on how I should implement my custom CacheManager

Oleg
  • 2,984
  • 8
  • 43
  • 71
  • If you check the result afterwards, using a cache is pretty pointless as you still have the performance hit of querying the database. You want to check before the method is executed. IMHO what you need is only a custom `KeyGenerator` instead of a full blown custom cache. Which does the check before you execute the method. – M. Deinum Jun 13 '16 at 08:06
  • Unfortunately, I won't be able to achieve this with KeyGenerator alone. There are some other manipulations I need to do with the result (like, remove some concerts based on its date if users don't match completely etc.), so the only solution is to subclass a cache manager and manipulate the date before returning it. – Oleg Jun 13 '16 at 13:24
  • Then it doesn't really make sense to use a cache, as you basically loose the benefits of a cache. – M. Deinum Jun 13 '16 at 13:57
  • I understand, but the 3rd party server I'm fetching the data from is extremely slow, so I want to minimize the number of requests I'm sending to it and instead use the data that is already cached on my server. Instead of making a new request I want to try to extract a subset of cached events I already have from before. – Oleg Jun 13 '16 at 14:35
  • What i also want to do is to fetch concerts for the entire month and don't make a new lookup when user requests concerts for a specific day within the same month, since day-overview can be extracted from the month-overview – Oleg Jun 13 '16 at 14:41

2 Answers2

2

I did not spend much time thinking about your requirements/use case, but I do think a custom CacheManager would work in this situation, assuming the "custom" CacheManager logic is correct.

So, by default, Spring looks for a bean in the context with the name "cacheManager" and uses it for all cached operations. In your configuration, you clearly have 2 "CacheManagers" defined...

@Bean(name = "eventsCacheManager")
public EventsCacheManager eventsCacheManager() {

    return new EventsCacheManager();
}

@Primary
@Bean(name = "cacheManager")
public CacheManager cacheManager() {
    return new EhCacheCacheManager(ehCacheCacheManager().getObject());
}

I.e. the "eventsCacheManager" (custom) and "cacheManager" (standard). Indeed, you even marked the "cacheManager" as primary (using the @Primary annotation). Of course, had you not done that, Spring would certainly have complained since more than 1 bean of a given type (i.e. CacheManager) was found in the context when performing auto-wiring (which defaults to auto-wire by type).

So, in order to call out which cache management strategy (i.e. CacheManager) to use at runtime with a particular service call, you also need to tell Spring which CacheManager (aka strategy) to use, like so...

@Override
@Cacheable(value = "concerts", cacheManager="eventsCacheManager")
public List<Event> getEvents(String eventsForUser, Date startDate, Date endDate) throws Exception {
    return fetchEventsFromTheServer(eventsForUser, startDate, endDate);
}

I.e. using the cacheManager attribute in the @Cacheable annotation to indicate which caching strategy to use.

See Spring's Reference Doc on Custom cache resolution for more details.

Hope this helps get you going.

John Blum
  • 7,381
  • 1
  • 20
  • 30
  • 1
    Thanks for your response, actually my problem is on how to implement custom cache manager? Please see my 'EventsCacheManager' snippet. Those two methods get called no problem, but I don't know what to write there so they return something... – Oleg Jun 13 '16 at 20:48
  • Right, I see. I too echo what @Louis Jacomet says as it is a more proactive and sensible approach to caching. However, it is a simple matter to "extend" the Spring CacheManager implementation along with return Cache abstractions, that actually do the work of interfacing with the underlying caching provider. I have built several examples in the past pertaining to various questions on SO. I will add additional comments with some examples. – John Blum Jun 14 '16 at 20:19
  • Here is an example of handling a collection of values cached individually in the Spring's Cache abstraction (uses custom Spring `CacheManager` and `Cache` implementations).. https://github.com/jxblum/spring-gemfire-tests/blob/master/src/test/java/org/spring/cache/CachingCollectionElementsIndividuallyWithConcurrentMapTest.java#L120-L297 – John Blum Jun 14 '16 at 20:23
  • Hmm, well I thought I had more examples (https://github.com/jxblum/spring-gemfire-tests/tree/master/src/test/java/org/spring/cache), but perhaps not. – John Blum Jun 14 '16 at 20:28
  • At any rate, I would question the need to extend the `CacheManager`, potentially returning a custom `Cache` implementation in all scenarios, and perhaps this one in particular, but to answer your questions... `getCache(..) ` returns a Cache representation "adapting" the underlying cache data structure (e.g. in GemFire, it is a `Region`) and `getCacheNames()` returns a list of all the "caches" (by name, of course) managed by the `CacheManager`. In come cases (e.g. `ConcurrentCacheManager`) a `Cache` can be created dynamically. In other cases... – John Blum Jun 14 '16 at 20:32
  • The underlying caching provider (e.g. GemFire) dictates that the underlying data structure (e.g. `Regions`) must be created and "identified" (named) before a `Cache` representation (wrapper/Adapter) can be created (that is if they cannot be "dynamically" created at runtime). – John Blum Jun 14 '16 at 20:33
1

From my understanding of your question, I believe you are looking at the problem the wrong way.

Instead of having to browse cache contents to extract derived information, you should insert into the cache the derived information at the time of loading the main data.

For example, when loading month based information, break it immediately into day based information and put that in a cache as well.

This description also should clearly indicate that what you want to do is beyond the capabilities of the Spring caching abstraction, as you need custom cache loading logic.

So I would not recommend hacking a CacheManager to hide that logic but instead do it from the data loading logic.

Louis Jacomet
  • 13,661
  • 2
  • 34
  • 43