我正在使用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"
这可能吗?怎么样?
提前致谢。
答案 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éphaneNicoll”的回答只是一个变种,但它可能对某人有用。我在这里写它并没有在IDE中检查它,但在我的项目中有类似的东西。
覆盖CacheResolver:
@Cacheable(value="products", key="#root.target.PRODUCTS", cacheResolver = "customCacheResolver")
实施您自己的缓存解析器,在内部搜索您缓存的项目并在那里进行工作
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...
}
详细了解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方法所使用的函数。