SpringBoot结合Redis 自定义数据缓存
缓存的主要目的是提升系统的性能、处理高并发情况,并减少对数据库的压力。通过将数据存储在内存中,当数据没有本质变化时,我们可以避免频繁地连接数据库进行查询。相反,我们可以首先尝试从缓存中读取数据,只有当缓存中未找到数据时,才会去数据库中查询。这样做可以显著减少数据库的读写操作,提高系统的性能和并发能力。
接下来我将写一个简单的缓存小栗子。。。
spring boot本身也有自带的缓存注解,但是我们现在来介绍的是通过Redis自定义的缓存
1. 依赖
我们需要引入redsi依赖和工具类依赖
版本自行添加
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
<version><!-- 指定版本号 --></version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version><!-- 指定版本号 --></version>
</dependency>
</dependencies>
2. 配置
在配置文件配置一下redsi
#redis 配置
redis:
host: ip地址
port: 6379
password: 密码,有就设置,没有就删除,默认是没有
connect-timeout: 10000 超时时间,自己看情况
3. 编写注解
/**
* 自定义缓存处理注解
*
* @Author 君子慎独
* @create 2021/01/25 16:41
*/
@Target(ElementType.METHOD) // 在方法上使用
@Retention(RetentionPolicy.RUNTIME) // 运行时有效
@Documented
public @interface Cache {
// 默认过期时间 5分钟
long expire() default 5 * 60 * 1000;
// 默认名称为空
String name() default "";
}
/**
* 处理缓存的切面类
*
* @Author 君子慎独
* @create 2021/01/25 16:45
*/
@Aspect // AOP切面
@Component // 注册为Spring组件
@Slf4j // 日志注解
public class CacheAspect {
// Redis模板类
@Autowired
private RedisTemplate<String, String> redisTemplate;
/**
* 切入点定义
*/
@Pointcut("@annotation(com.qwt.test.common.cache.Cache)")
public void cachePointcut() {
}
/**
* 环绕通知方法,处理缓存逻辑
*
* @param joinPoint 切入点信息
* @return 方法执行结果
* @throws Throwable 可能抛出的异常
*/
@Around("cachePointcut()")
public Object cacheAround(ProceedingJoinPoint joinPoint) throws Throwable {
log.info("进入缓存处理");
try {
// 获取方法签名
Signature signature = joinPoint.getSignature();
// 获取类名
String className = joinPoint.getTarget().getClass().getSimpleName();
// 获取方法名
String methodName = signature.getName();
// 获取方法参数
Object[] args = joinPoint.getArgs();
// 构建缓存Key
String cacheKey = buildCacheKey(className, methodName, args);
// 从缓存中获取结果
String cachedResult = redisTemplate.opsForValue().get(cacheKey);
if (StringUtils.isNotEmpty(cachedResult)) {
log.info("命中缓存:{}.{}", className, methodName);
return JSON.parseObject(cachedResult, Result.class);
}
// 执行方法并获取结果
Object result = joinPoint.proceed();
// 将结果存入缓存
storeResultInCache(cacheKey, result);
log.info("存入缓存:{}.{}", className, methodName);
return result;
} catch (Throwable throwable) {
log.error("缓存处理发生异常:{}", throwable.getMessage());
throwable.printStackTrace();
throw throwable;
}
}
/**
* 构建缓存Key
*
* @param className 类名
* @param methodName 方法名
* @param args 方法参数
* @return 缓存Key
*/
private String buildCacheKey(String className, String methodName, Object[] args) {
StringBuilder keyBuilder = new StringBuilder();
keyBuilder.append(className).append("::")
.append(methodName).append("::");
if (args != null && args.length > 0) {
for (Object arg : args) {
if (arg != null) {
keyBuilder.append(arg.toString());
}
}
}
return DigestUtils.md5Hex(keyBuilder.toString());
}
/**
* 将方法执行结果存入缓存
*
* @param cacheKey 缓存Key
* @param result 方法执行结果
*/
private void storeResultInCache(String cacheKey, Object result) {
long expireTime = getCacheExpireTime(result);
String jsonResult = JSON.toJSONString(result);
redisTemplate.opsForValue().set(cacheKey, jsonResult, Duration.ofMillis(expireTime));
}
/**
* 获取缓存过期时间
*
* @param result 方法执行结果
* @return 缓存过期时间
*/
private long getCacheExpireTime(Object result) {
Method method = ((MethodSignature) joinPoint.getSignature()).getMethod();
Cache annotation = method.getAnnotation(Cache.class);
if (annotation != null) {
return annotation.expire();
}
// 默认过期时间 5分钟
return 5 * 60 * 1000;
}
}
注意:
redisKey :这个必须保证唯一 除开类名、方法名和缓存名称,保证这个key唯一的就是参数,所以在给没有参数的方法使用缓存时请慎重考虑,不然就会出现不同的用户之间获取到的数据一样和两个用户之间交换获取对方数据,这是非常致命的
4. 测试
这个注解实在controller层使用的,因为是测试就不要关心参数问题,所以我们这里测试就不给参数了
@PostMapping("/test")
@Cache(name = "test", expire = 5000)
public Result test() throws InterruptedException {
// 为了展现缓存效果,这里我们加个睡眠
Thread.sleep(3000);
return Result.success("缓存哈哈哈!");
}
使用postman查看
第一次:
第二次:
缓存的效果就可以很明显的看出来了,那么这样我们非常简单的数据缓存就完成了
建议是在数据不经常修改或经常查询但是一段时间内不会改变的时候才使用,如果频繁更改的其实使用缓存的意义不大,当然也可以使用不过相对而言我这个就不适合了,想要更加好的代码来支持