How to use cache with queries on different fields/keys?

627 Views Asked by At

I am implementing a Spring MVC based web application where I have added caching, but now face a problem in terms of how to operate on the cache when changes are applied to underlying database.

I have been studying the documentation from available at Ehcache (http://ehcache.org/documentation), but failed to find any good examples to solve my problem.

Let's say I have a DAO class where I have chosen to apply a cache on two methods that return a list of objects (non-compileable pseudocode):

@Repository
public class MyEntityDao {

   @Autowired
   DataSource ds;

   @Autowired
   EhCacheCacheManager cacheManager;

   @Autowired
   CacheKeyGenerator keyGenerator;

   @Cacheable("myEntityCache")
   public List<MyEntity> findByFieldAlpha(String fieldAlpha) {
       return ds.query("select * from MyEntity where fieldAlpha = #fieldAlpha").execute();
   }

   @Cacheable("myEntityCache")
   public List<MyEntity> findByFieldBeta(String fieldBeta) {
       return ds.query("select * from MyEntity where fieldBeta = #fieldBeta").execute();
   }

   public void deleteByFieldAlpha(String fieldAlpha) {
      ds.query("delete from MyEntity where fieldAlpha = #fieldAlpha").execute();
   }

   public void deleteByFieldBeta(String fieldBeta) {
      ds.query("delete from MyEntity where fieldBeta = #fieldBeta").execute();
   }

   public void store(MyEntity myEntity) {
      ds.insert(myEntity).execute();
   }
}

I am using a custom KeyGenerator, so I know how keys when calling a method are generated:

public class CacheKeyGenerator implements KeyGenerator {

   @Override
   public Object generate(final Object target, final Method method, final Object... params) {
       return generateCacheKey(method.getName(), params);
   }

   public String generateCacheKey(final String methodName, final Object... params) {
       StringBuilder key = new StringBuilder();
       key.append(methodName);
       for (Object o : params) {
          key.append("_").append(o.toString());
       }
       return key.toString();
   }
}

The obvious problem I need to solve is how I should implement the cache modification policy when data is added or removed to the underlying database.

For the store method I find the problem quite trivial to solve:

 ...

 public void store(MyEntity myEntity) {
    boolean success = ds.insert(myEntity).execute();
    if (success) {
       Cache cache = cacheManager.getCache("myEntityCache")

       String keyFieldAlpha = keyGenerator.generateCacheKey("findByFieldAlpha", myEntity.getFieldAlpha());
       List cacheListFieldAlpha = (List) cache.get(keyFieldAlpha).getObjectValue();
       cacheListFieldAlpha.add(myEntity);

       String keyFieldBeta = keyGenerator.generateCacheKey("findByFieldBeta", myEntity.getFieldBeta());
       List cacheListFieldBeta = (List) cache.get(keyFieldBeta).getObjectValue();
       cacheListFieldBeta.add(myEntity);
    }
 }

But for the delete-methods things get much more complicated.

This is one implementation I have come up with so far, but it seems to be far more complicated (costly) than it should be. Anyone have any valuable input if my design patterns are correct, or if should solve my problem in any other way?

public void deleteByFieldAlpha(String fieldAlpha) {
  List<MyEntity> myEntititesToBeDeleted = this.findByFieldAlpha(fieldAlpha);
  boolean success = ds.query("delete from MyEntity where fieldAlpha = #fieldAlpha").execute();
  if (success) {
     Cache cache = cacheManager.getCache("myEntityCache")

     String keyFieldAlpha = keyGenerator.generateCacheKey("findByFieldAlpha", fieldAlpha);
     cache.remove(keyFieldAlpha);

     for (MyEntity myEntity : myEntititesToBeDeleted) {
        String keyFieldBeta = keyGenerator.generateCacheKey("findByFieldBeta", myEntity.getFieldBeta());
        List cacheListFieldBeta = (List) cache.get(keyFieldBeta).getObjectValue();
        cacheListFieldBeta.remove(myEntity); 
     }
  }
}

Perhaps this is the correct way of solving it? I haven't stumbled upon any recipes that solves these problems so I am at my best guess here.

2

There are 2 best solutions below

0
On

I'll give you my opinion. It seems to be (almost) the correct aproach. I think that after delete operation you should empty all the cache and start again. Usualy, after data changing operations the cache is reset. That's done because of the table joins. Doesn't seem to be your case because you have only two queries and no joins, but usualy the cache is cleaned and restarted. Doing this way you don't need to scan over the cache that refers to "fieldBeta" after changes in "fieldAlfa". Hope somethig from this could help you. BTW, MyBatis works that way.

1
On

Use Ehcache search API, probably in operator would be useful in you case.