基于SpringCache实现分页查询下的缓存查询

假设一个场景,目录菜单下会有许多商品,我们需要对各菜单下的商品进行分页查询。

我一开始觉得非常简单,在查询方法上添加以下注解

@Cacheable(value = "product", key = "'menu_' + #request.menuId", condition = "#request.menuId != null", unless = "#result.list.size() == 0")

在增删改方法上添加以下注解

@CacheEvict(value = "product", key = "'menu_' + #request.menuId")

但很快发现了问题,因为其中不含page,意味着每次分页查询相同菜单目录时的key是不变的,这导致每次查询的结果都一致

于是我做了以下改动

@Cacheable(value = "product", key = "'menu_' + #request.menuId + ':' + #request.page + '_' + #request.pageSize", condition = "#request.menuId != null", unless = "#result.list.size() == 0")

查询的问题是解决了,但是增删改并不知道自己属于哪一页,这导致增删改全部失效,但@CacheEvict中唯一能在此情况下删除缓存的方法就是allEntries = true

@CacheEvict(value = "product", key = "'menu_' + #request.menuId", allEntries = true)

但是这会影响其他缓存,把所有缓存都删掉,因此使用@CacheEvict是无法解决该问题的

既然是对某个方法进行相应删除缓存操作,我们可以通过aop的方式自定义方法删除缓存

@Target({java.lang.annotation.ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface CacheRemove {
 
    String cacheName() default "";
 
    String key() default "";
 
    String prefix() default "";
 
}

cacheName用于指出缓存组件,key指定一个spEL表达式,通过spEL表达式获取方法参数,prefix表示key前缀字符,以防止单单存在的id会存在重复而误删其他缓存,prefix与key拼接表示需要删除的缓存对应的key

@Aspect
@Component
public class CacheRemoveAspect {
 
    @Resource
    RedisCache redisCache;
 
    @Pointcut(value = "@annotation(com.yxy.xxx.annotation.CacheRemove)")
    public void pointcut() {
    }
 
    @Around("pointcut()")
    private Object process(ProceedingJoinPoint joinPoint) throws Throwable {
 
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        // 1.获取注解参数值 SpEL表达式
        CacheRemove cacheBatchEvict = methodSignature.getMethod().getAnnotation(CacheRemove.class);
        String cacheName = cacheBatchEvict.cacheName();
 
        String spEL = cacheBatchEvict.key();
        if (!StringUtils.hasText(spEL)) {
            return null;
        }
        // 2.获取目标方法参数
        Object cacheKey = generateKeyListBySpEL(spEL, joinPoint);
        if (cacheKey == null) {
            return null;
        }
        String partKey = cacheBatchEvict.prefix() + cacheKey;
        // 3.清除缓存
        Collection<String> keys = redisCache.keys(cacheName + "::*");
        for (String key : keys) {
            if (key.contains(partKey)) {
                redisCache.deleteObject(key);
            }
        }
        // 4.执行目标方法
        return joinPoint.proceed();
    }
 
    public String generateKeyListBySpEL(String spEL, ProceedingJoinPoint joinPoint) {
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        Method method = methodSignature.getMethod();
        // 创建解析器
        SpelExpressionParser parser = new SpelExpressionParser();
        // 获取表达式
        Expression expression = parser.parseExpression(spEL);
        // 设置解析上下文
        EvaluationContext context = new StandardEvaluationContext();
        Object[] args = joinPoint.getArgs();
        // 获取运行时参数名称
        DefaultParameterNameDiscoverer discover = new DefaultParameterNameDiscoverer();
        String[] parameterNames = discover.getParameterNames(method);
        assert parameterNames != null;
        for (int i = 0; i < parameterNames.length; i++) {
            context.setVariable(parameterNames[i], args[i]);
        }
        // 解析
        Object value = expression.getValue(context);
 
        Assert.notNull(value, "SPEL解析错误");
        return value.toString();
    }
 
 
}

通过拼接key得到某类下的唯一标识再进行模糊查询删除

我们只需在增删改方法上加上该注解即可实现

@CacheRemove(cacheName = "product", key = "#request.menuId", prefix = "menu_")
  • 1
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值