如何更新/删除已在项集合中缓存的项目

时间:2014-07-24 18:07:16

标签: spring caching ehcache spring-3 spring-cache

我正在使用Spring和EhCache

我有以下方法

@Override
@Cacheable(value="products", key="#root.target.PRODUCTS")
public Set<Product> findAll() {
    return new LinkedHashSet<>(this.productRepository.findAll());
}

我有其他使用@Cacheable和@CachePut以及@CacheEvict的方法。

现在,假设数据库返回100个产品并通过key="#root.target.PRODUCTS"缓存,然后其他方法将插入 - 更新 - 删除项目放入数据库。因此,通过key="#root.target.PRODUCTS"缓存的产品不再相同,例如数据库。

我的意思是,检查以下两种方法,他们能够更新/删除某个项目,而相同项目会缓存在另一个key="#root.target.PRODUCTS"

@Override
@CachePut(value="products", key="#product.id")
public Product update(Product product) {
    return this.productRepository.save(product);
}

@Override
@CacheEvict(value="products", key="#id")
public void delete(Integer id) {
    this.productRepository.delete(id);
}

我想知道是否可以通过key="#root.target.PRODUCTS"更新/删除位于缓存中的项目,如果产品已更新,则为100;如果产品已删除,则为499。

我的观点是,我想避免以下情况:

@Override
@CachePut(value="products", key="#product.id")
@CacheEvict(value="products", key="#root.target.PRODUCTS")
public Product update(Product product) {
    return this.productRepository.save(product);
}

@Override
@Caching(evict={
        @CacheEvict(value="products", key="#id"),
        @CacheEvict(value="products", key="#root.target.PRODUCTS")
})
public void delete(Integer id) {
    this.productRepository.delete(id);
}

我不想再将500或499种产品缓存到key="#root.target.PRODUCTS"

这可能吗?怎么样?

提前致谢。

4 个答案:

答案 0 :(得分:5)

使用缓存抽象缓存集合与底层缓存系统正在执行的操作重复。并且因为这是重复的,事实证明你必须以某种方式在你自己的代码中使用某种重复(该集合的重复键是其明显的表示)。因为有重复,你必须以某种方式同步状态

如果你真的需要访问整个集合和单个元素,那么你应该使用最简单的一个快捷方式。首先,您应该确保您的缓存包含所有不明显的元素。实际上远非如此。考虑到你有:

//EhCacheCache cache = (EhCacheCache) cacheManager.getCache("products");


@Override
public Set<Product> findAll() {
    Ehcache nativeCache = cache.getNativeCache();
    Map<Object, Element> elements = nativeCache.getAll(nativeCache.getKeys());
    Set<Product> result = new HashSet<Product>();
    for (Element element : elements.values()) {
        result.add((Product) element.getObjectValue());
    }
    return Collections.unmodifiableSet(result);
}

elements结果实际上是一个延迟加载的映射,因此对values()的调用可能会抛出异常。你可能想要遍历键或其他东西。

您必须记住,缓存抽象可以简化对底层缓存基础结构的访问,并且绝不会替换它:如果您必须直接使用API​​,这可能是您在某种程度上必须执行的操作。

现在,如果您认为我们可以改进该区域的缓存抽象,我们可以将转换保留在SPR-12036。谢谢!

答案 1 :(得分:3)

我认为这样的事情可以起作用......实际上,如果“StéphaneNic​​oll”的回答只是一个变种,但它可能对某人有用。我在这里写它并没有在IDE中检查它,但在我的项目中有类似的东西。

  1. 覆盖CacheResolver:

    @Cacheable(value="products", key="#root.target.PRODUCTS", cacheResolver = "customCacheResolver")
    
  2. 实施您自己的缓存解析器,在内部搜索您缓存的项目并在那里进行工作

    public class CustomCacheResolver implements CacheResolver{
      private static final String CACHE_NAME = "products";
      @Autowired(required = true) private CacheManager cacheManager;
    
    @SuppressWarnings("unchecked")
    @Override
    public Collection<? extends Cache> resolveCaches(CacheOperationInvocationContext<?> cacheOperationInvocationContext) {
      // 1. Take key from query and create new simple key
      SimpleKey newKey;
      if (cacheOperationInvocationContext.getArgs().length != null) { //optional
        newKey = new SimpleKey(args); //It's the key of cached object, which your "@Cachable" search for           
      } else {
        //Schould never be... DEFAULT work with cache if something wrong with arguments
        return new ArrayList<>(Arrays.asList(cacheManager.getCache(CACHE_NAME)));
      }
    
      // 2. Take cache
      EhCacheCache ehCache = (EhCacheCache)cacheManager.getCache(CACHE_NAME);  //this one we bringing back                
      Ehcache cache = (Ehcache)ehCache.getNativeCache();  //and with this we working        
      // 3. Modify existing Cache if we have it
      if (cache.getKeys().contains(newKey) && YouWantToModifyIt) {
        Element element = cache.get(key);
        if (element != null && !((List<Products>)element.getObjectValue()).isEmpty()) {
        List<Products> productsList = (List<Products>)element.getObjectValue();
        // ---**--- Modify your "productsList" here as you want. You may now Change single element in this list.                   
        ehCache.put(key, anfragenList); //this method NOT adds cache, but OVERWRITE existing                            
      // 4. Maybe "Create" new cache with this key if we don't have it
      } else {
        ehCache.put(newKey, YOUR_ELEMENTS);
      }
      return new ArrayList<>(Arrays.asList(ehCache));  //Bring all back - our "new" or "modified" cache is in there now...
    }
    
  3. 详细了解EhCache的CRUD:EhCache code samples

    希望它有所帮助。抱歉我的英语:(

答案 2 :(得分:2)

我认为有一种方法可以从spring的底层缓存结构中读取集合。您可以从基础 ConcurrentHashMap 作为键值对检索集合,而无需使用 EhCache 或其他任何方法。然后,您可以更新或删除该集合中的条目,然后也可以更新缓存。以下示例可能会有所帮助:

import com.crud.model.Post;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
import org.springframework.cache.interceptor.CacheOperationInvocationContext;
import org.springframework.cache.interceptor.CacheResolver;
import org.springframework.cache.interceptor.SimpleKey;
import org.springframework.stereotype.Component;
import java.util.*;


@Component
@Slf4j
public class CustomCacheResolver implements CacheResolver {

private static final String CACHE_NAME = "allPost";

@Autowired
private CacheManager cacheManager;

@SuppressWarnings("unchecked")
@Override
public Collection<? extends Cache> resolveCaches(CacheOperationInvocationContext<?> cacheOperationInvocationContext) {

    log.info(Arrays.toString(cacheOperationInvocationContext.getArgs()));

    String method = cacheOperationInvocationContext.getMethod().toString();
    Post post = null;
    Long postId = null;
    if(method.contains("update")) {
        //get the updated post
        Object[] args = cacheOperationInvocationContext.getArgs();
        post = (Post) args[0];
    }
    else if(method.contains("delete")){
        //get the post Id to delete
        Object[] args = cacheOperationInvocationContext.getArgs();
        postId = (Long) args[0];
    }


    //read the cache
    Cache cache = cacheManager.getCache(CACHE_NAME);

    //get the concurrent cache map in key-value pair
    assert cache != null;
    Map<SimpleKey, List<Post>> map = (Map<SimpleKey, List<Post>>) cache.getNativeCache();

    //Convert to set to iterate
    Set<Map.Entry<SimpleKey, List<Post>>> entrySet = map.entrySet();
    Iterator<Map.Entry<SimpleKey, List<Post>>> itr = entrySet.iterator();

    //if a iterated entry is a list then it is our desired data list!!! Yayyy
    Map.Entry<SimpleKey, List<Post>> entry = null;
    while (itr.hasNext()){
        entry = itr.next();
        if(entry instanceof List) break;
    }

    //get the list
    assert entry != null;
    List<Post> postList = entry.getValue();

    if(method.contains("update")) {
        //update it
        for (Post temp : postList) {
            assert post != null;
            if (temp.getId().equals(post.getId())) {
                postList.remove(temp);
                break;
            }
        }
        postList.add(post);
    }
    else if(method.contains("delete")){
        //delete it
        for (Post temp : postList) {
            if (temp.getId().equals(postId)) {
                postList.remove(temp);
                break;
            }
        }
    }


    //update the cache!! :D
    cache.put(entry.getKey(),postList);

    return new ArrayList<>(Collections.singletonList(cacheManager.getCache(CACHE_NAME)));
}
}

以下是使用 CustomCacheResolver

的方法
@Cacheable(key = "{#pageNo,#pageSize}")
public List<Post> retrieveAllPost(int pageNo,int pageSize){ // return list}

@CachePut(key = "#post.id",cacheResolver = "customCacheResolver")
public Boolean updatePost(Post post, UserDetails userDetails){ //your logic}

@CachePut(key = "#postId",cacheResolver = "customCacheResolver")
public Boolean deletePost(Long postId,UserDetails userDetails){ // your logic}

@CacheEvict(allEntries = true)
public Boolean createPost(String userId, Post post){//your logic}

希望它有助于手动操作spring应用程序缓存!

答案 3 :(得分:0)

虽然我没有看到任何简单的方法,但您可以通过提供cache decorator来覆盖Ehcache缓存功能。最有可能的是,您希望使用EhcahceDecoratorAdapter来增强EhCacheCache put和evict方法所使用的函数。