Search code examples
javaspringspring-bootjava-8caffeine

Reuse caffeine cache key


I have implemented a cache using caffeine. I want it to expire after 1 day, and that works fine. The problem, is that if I try to get data from the cache using a modified query (modified in terms of parameter values), I always get the same results. I want to get the same results only if I have executed a query with the same parameter values (e.g. same date range). My understanding is that I should not be re-using the same cache-key, but I m not sure how not to. This is the implementation:

@Service
public class SomeServiceImpl implements SomeService {
  private static final String CACHE_KEY = "some-key";
  private Cache<String, List<SomeVO>> someCache;



  public RecentHighestSlotWinsServiceImpl() {

    someCache= Caffeine.newBuilder().maximumSize(100).expireAfterWrite(1, TimeUnit.DAYS).build();
  }

  @Override
  public List<SomeVO> checkCacheForData(Instant startDate, Instant endDate) {

  List<SomeVO> someList= someCache.getIfPresent(CACHE_KEY);
  if (someList!= null) {
      return someList;
  } else {
      log.info("Loading from DB ");
      List<SomeVO> templateResult= getData(startDate, endDate);
      if (templateResult.isSuccessAndNotNull()) {
          someCache.put(CACHE_KEY, Objects.requireNonNull(templateResult.getData()));
      }
      return templateResult;
  }
}

Solution

  • Couple of ways to deal with this:

    1. As @Louis Wasserman mentioned, have something like this:
    import java.time.Instant;
    import java.util.Objects;
    
    public class CacheKey {
      private Instant startDate;
      private Instant endDate;
    
      public CacheKey() {
      }
    
      public CacheKey(Instant startDate, Instant endDate) {
        this.startDate = startDate;
        this.endDate = endDate;
      }
    
      public Instant getStartDate() {
        return startDate;
      }
    
      public void setStartDate(Instant startDate) {
        this.startDate = startDate;
      }
    
      public Instant getEndDate() {
        return endDate;
      }
    
      public void setEndDate(Instant endDate) {
        this.endDate = endDate;
      }
    
      @Override
      public boolean equals(Object o) {
        if (this == o)
          return true;
        if (o == null || getClass() != o.getClass())
          return false;
        CacheKey cacheKey = (CacheKey) o;
        return Objects.equals(startDate, cacheKey.startDate) &&
            Objects.equals(endDate, cacheKey.endDate);
      }
    
      @Override
      public int hashCode() {
        return Objects.hash(startDate, endDate);
      }
    }
    

    and in your method checkCacheForData use:

    @Service
    public class SomeServiceImpl implements SomeService {
      private Cache<CacheKey, List<SomeVO>> someCache;
    
    
    
      public RecentHighestSlotWinsServiceImpl() {
    
        someCache= Caffeine.newBuilder().maximumSize(100).expireAfterWrite(1, TimeUnit.DAYS).build();
      }
    
      @Override
      public List<SomeVO> checkCacheForData(Instant startDate, Instant endDate) {
    
      List<SomeVO> someList= someCache.getIfPresent(new CacheKey(startDate,endDate));
      if (someList!= null) {
          return someList;
      } else {
          log.info("Loading from DB ");
          List<SomeVO> templateResult= getData(startDate, endDate);
          if (templateResult.isSuccessAndNotNull()) {
              someCache.put(new CacheKey(startDate,endDate), Objects.requireNonNull(templateResult.getData()));
          }
          return templateResult;
      }
    }
    
    1. Instead of Having a separate object you can concatenate the string representation of start date and end date and use that as cache key.
    @Service
    public class SomeServiceImpl implements SomeService {
      private Cache<String, List<SomeVO>> someCache;
    
    
    
      public RecentHighestSlotWinsServiceImpl() {
    
        someCache= Caffeine.newBuilder().maximumSize(100).expireAfterWrite(1, TimeUnit.DAYS).build();
      }
    
      @Override
      public List<SomeVO> checkCacheForData(Instant startDate, Instant endDate) {
    
    String cacheKey = startDate.toString() + endDate.toString();
      List<SomeVO> someList= someCache.getIfPresent(cacheKey);
      if (someList!= null) {
          return someList;
      } else {
          log.info("Loading from DB ");
          List<SomeVO> templateResult= getData(startDate, endDate);
          if (templateResult.isSuccessAndNotNull()) {
              someCache.put(cacheKey, Objects.requireNonNull(templateResult.getData()));
          }
          return templateResult;
      }
    }