0
votes

I have a method that pulls in a bunch of data. This has the potential to take a decent amount of time due to the large data set and the amount of computation required. The method that does this call will be used many times. The result list should return the same results each time. With that being said, I want to cache the results, so I only have to do that computation once. I'm supposed to use the CacheBuilder class. The script I have is essentially something like:

class CheckValidValues implements AValidValueInterface {
    private ADataSourceInterface dataSource;

    public CheckValidValues(ADataSourceInterface dataSource) {
        this.dataSource = dataSource;
    }

    @Override
    public void validate(String value) {
        List<?> validValues = dataSource.getValidValues();

        if (!validValues.contains(value)) {
            // throw an exception

So I'm not even sure where I should be putting the caching method (i.e. in the CheckValidValues class or the getValidValues() method in dataSource. Also, I'm not entirely sure how you can add code into one of the methods without it instantiating the cache multiple times. Here's the route that I'm trying to take, but have no idea if it's correct. Adding above the List validValues = dataSource.getValidValues() line:

    LoadingCache<String, List<?>> validValuesCache = CacheBuilder.newBuilder()
            .expireAfterAccess(30, TimeUnit.SECONDS)
            .build(
                    new CacheLoader<String, List<?>>() {
                        public List<?> load(@Nonnull String validValues) {
                            return valuesSupplier.getValidValues();
                        }
                    }
            );

Then later, I'd think I could get that value with:

validValuesCache.get("validValues");

What I think should happen there is that it will do the getValidValues command and store that in the cache. However, if this method is being called multiple times, then, to me, that would mean it would create a new cache each time.

Any idea what I should do for this? I simply want to add the results of the getValidValues() method to cache so that it can be used in the next iteration without having to redo any computations.

2
Why expire after 30s if the result list does not change?Jean Logeart
just so it doesn't continue to take up memory when the call is no longer required? Honestly, that's just me throwing in a time.user2869231
Are you looking for the CacheLoader.loadAll method? "This method should be overriden when bulk retrieval is significantly more efficient than many individual lookups"Andy Turner
It would make sense since the method is returning all the data via a method, so possiblyuser2869231
would it make more sense to use caching with Spring proxy? Which I don't know how to do either hehe the project does use Springuser2869231

2 Answers

4
votes
  1. You only want to cache a single value, the list of valid values. Use Guavas' Suppliers.memoizeWithExpiration(Supplier delegate, long duration, TimeUnit unit)

  2. Each valid value is only existing once. So your List is essentially a Set. Back it by a HashSet (or a more efficient variant in Guava). This way the contains() is a hash table lookup instead of a sequential search inside the list.

3
votes

We use Guava and Spring-Caching in a couple of projects where we defined the beans via Java configuration like this:

@Configuration
@EnableCaching
public class GuavaCacheConfig {

    ...

    @Bean(name="CacheEnabledService")
    public SomeService someService() {
        return new CacheableSomeService();
    }

    @Bean(name="guavaCacheManager")
    public CacheManager cacheManager() {
      // if different caching strategies should occur use this technique:
      // http://www.java-allandsundry.com/2014/10/spring-caching-abstraction-and-google.html
      GuavaCacheManager guavaCacheManager = new GuavaCacheManager();
      guavaCacheManager.setCacheBuilder(cacheBuilder());
      return guavaCacheManager;
    }

    @Bean(name = "expireAfterAccessCacheBuilder")
    public CacheBuilder<Object, Object> cacheBuilder() {
      return CacheBuilder.newBuilder()
          .recordStats()
          .expireAfterAccess(5, TimeUnit.SECONDS);
    }

    @Bean(name = "keyGenerator")
    public KeyGenerator keyGenerator() {
        return new CustomKeyGenerator();
    }

    ...
}

Note that the code above was taken from one of our integration tests.

The service, which return values should be cached is defined as depicted below:

@Component
@CacheConfig(cacheNames="someCache", keyGenerator=CustomKeyGenerator.NAME, cacheManager="guavaCacheManager")
public class CacheableService {

    public final static String CACHE_NAME = "someCache";

    ...

    @Cacheable
    public <E extends BaseEntity> E findEntity(String id) {
        ...
    }

    ...

    @CachePut
    public <E extends BaseEntity> ObjectId persist(E entity) {
        ...
    }

    ...
}

As Spring-Caching uses an AOP approach, on invoking a @Cacheable annotated method Spring will first check if already a previous stored return value is available in the cache for the invoked method (depending on the cache key; we use a custom key generator therefore). If no value is yet available, Spring will invoke the actual service method and store the return value into the local cache which is available on subsequent calls.

@CachePut will always execute the service method and put the return value into the cache. This is useful if an existing value inside the cache should be replaced by a new value in case of an update for example.