在查询类开发中我们有使用缓存的场景,一般可以使用Redis作为缓存,来缓解数据库如MySQL的压力。使用缓存的步骤为:
“(1)从Redis缓存中获取数据,如果存在数据,直接返回值。
(2)如果不存在,执行数据库的查询方法
(3)将数据库中的值放入缓存
”NO CODE NO BB,代码如下
//a.从缓存中获取 String value = redisTemplate.opsForValue().get(key); if (value != null) { log.info("从缓存中读取到值:{}", value); return value; } //b.从数据库中查询 List<Member> members = memberMapper.listByName(name); //c.同步value到缓存 value = JSONArray.toJSONString(members); redisTemplate.opsForValue().set(key, value, expireTimes, TimeUnit.SECONDS); return value;如上代码,这里有个问题,我们只是要做个查询而已,也就是只要b行的代码,其他代码不是业务代码,不应该由开发人员去操心。那我们何不用注解的形式代替a和c代码呢。
SpringBoot提供了现成可用的缓存注解@Cacheble。
控制台结果
members:[Member(id=1805590839001216, name=zhouzhou, code=109, annotationParam=null)]上面代码发现使用Spring缓存注解的缓存失效时间还要在配置类中进行配置。于是我在想为什么这个失效时间不做成注解的这一项属性呢,这样自定义失效时间就比较方便了。
参数说明如下:
“key():缓存的key,一般是一个动态的参数值
vaule():缓存的value,value::key为Redis缓存中拼接的KEY
expireTimes():缓存失效时间,默认
semaphoreCount():共享锁,并发下允许访问的线程数,用于保护数据库。默认为Integer.MAX_VALUE
”首先创建切面类
@Component @Aspect @Slf4j public class CacheAspect { ... }创建横切面,为注解CustomizeCache添加功能。
@Pointcut("@annotation(com.lvshen.demo.redis.cache.CustomizeCache)") public void cachePointcut() { }开发缓存功能,定义@Around
@Around("cachePointcut()") public Object doCache(ProceedingJoinPoint point) { ... }获取方法上注解的内容
Method method = point.getTarget().getClass().getMethod(signature.getName(), signature.getMethod().getParameterTypes()); CustomizeCache annotation = method.getAnnotation(CustomizeCache.class); String keyEl = annotation.key(); String prefix = annotation.value(); long expireTimes = annotation.expireTimes(); int semaphoreCount = annotation.semaphoreCount();解析SpringEL表达式
Object[] args = point.getArgs(); DefaultParameterNameDiscoverer discoverer = new DefaultParameterNameDiscoverer(); String[] parameterNames = discoverer.getParameterNames(method); for (int i = 0; i < parameterNames.length; i++) { context.setVariable(parameterNames[i], args[i].toString()); }拼接Redis KEY
//解析 String key = prefix + "::" + expression.getValue(context).toString();判断缓存中是否存在
value = redisTemplate.opsForValue().get(key); if (value != null) { log.info("从缓存中读取到值:{}", value); return value; }自定义组件-创建限流令牌
semaphore = new Semaphore(semaphoreCount); boolean tryAcquire = semaphore.tryAcquire(3000L, TimeUnit.MILLISECONDS); if (!tryAcquire) { //log.info("当前线程【{}】获取令牌失败,等待其他线程释放令牌", Thread.currentThread().getName()); throw new RuntimeException(String.format("当前线程【%s】获取令牌失败,等带其他线程释放令牌", Thread.currentThread().getName())); }如果缓存没有数据,则执行原本方法。
value = point.proceed();同步value到缓存
redisTemplate.opsForValue().set(key, value, expireTimes, TimeUnit.SECONDS);最后释放令牌
} finally { if (semaphore == null) { return value; } else { semaphore.release(); } }测试结果
members:[Member(id=15, name=lvshen99, code=200, annotationParam=null)]Redis上显示
我们也可以自定义缓存失效时间,如设置失效时间
@CustomizeCache(value = "member", key = "#name",expireTimes = 3600) public List<Member> listByNameSelfCache(String name) { return memberMapper.listByName(name); }失效时间为3600ms,如图,显示3585ms是因为截图的时候过了15ms。
源码地址如下:
完整源码Github地址:https://github.com/lvshen9/demo/tree/lvshen-dev/src/main/java/com/lvshen/demo/redis/cache我写出这样干净的代码,老板直夸我
云南丽江旅游攻略
使用ThreadLocal怕内存泄漏?
Java进阶之路思维导图
程序员必看书籍推荐
3万字的Java后端面试总结(附PDF)
扫码二维码,获取更多精彩。或微信搜Lvshen_9,可后台回复获取资料
1.回复"java" 获取java电子书; 2.回复"python"获取python电子书; 3.回复"算法"获取算法电子书; 4.回复"大数据"获取大数据电子书; 5.回复"spring"获取SpringBoot的学习视频。 6.回复"面试"获取一线大厂面试资料 7.回复"进阶之路"获取Java进阶之路的思维导图 8.回复"手册"获取阿里巴巴Java开发手册(嵩山终极版) 9.回复"总结"获取Java后端面试经验总结PDF版 10.回复"Redis"获取Redis命令手册,和Redis专项面试习题(PDF) 11.回复"并发导图"获取Java并发编程思维导图(xmind终极版)另:点击【我的福利】有更多惊喜哦。