SpringCache使用详解及与Redis的整合
前言
明明我们项目中使用最多的缓存技术就是Redis,用 Redis 就完全就可以搞定缓存的问题了,为什么还有一个SpringCache,以及SpringCache和Redis之间的区别。
1. 为什么要使用缓存
- 缓存是将数据直接存入内容中,读取效率比数据库的更高
- 缓存可以有效地降低数据库压力,为数据库减轻负担
2. 为什么要使用 SpringCache
先看一下我们使用缓存步骤:
- 查寻缓存中是否存在数据,如果存在则直接返回结果
- 如果不存在则查询数据库,查询出结果后将结果存入缓存并返回结果
- 数据更新时,先更新数据库
- 然后更新缓存,或者直接删除缓存
此时我们会发现一个问题,所有我们需要使用缓存的地方都必须按照这个步骤去书写,这样就会出现很多逻辑上相似的代码。并且我们程序里面也需要显示的去调用第三方的缓存中间件的 API,如此一来就大大的增加了我们项目和第三方中间件的耦合度。就以 Redis 为列,如下图所示:

图中代码所示,就是我们上面描述的使用Redis作为缓存中间件来进行缓存的实列,我们不难发现,我们的查询和存储时都是使用到了SpringBoot整合Redis后的相关 API 的,并且项目中所有的使用缓存的地方都会如此使用,这样子提升了代码的复杂度,我们程序员更应该关注的是业务代码,因此我们需要将查询缓存和存入缓存这类似的代码封装起来用框架来替我们实现,让我们更好的去处理业务逻辑。
那么我们如何让框架去帮我们自动处理呢,这不就是典型的AOP思想吗?
是的,Spring Cache就是一个这样的框架。它利用了AOP,实现了基于注解的缓存功能,并且进行了合理的抽象,业务代码不用关心底层是使用了什么缓存框架,只需要简单地加一个注解,就能实现缓存功能了。而且Spring Cache也提供了很多默认的配置,用户可以 3 秒钟就使用上一个很不错的缓存功能。
使用了Spring Cache框架后使用缓存实列,如下图所示:

我们只需要将我们的方法添加一个注解就可以将方法返回结果直接存入缓存,并不需要手动去进行设置,是不是大大的简化了代码。
3. SpringBoot 整合 SpringCache
3.1. 说明
spEl 语法说明==>官方文档
官网springcache 介绍目录,官网的注解一共有 5 个,如图:

| 注解 | 说明 |
|---|---|
| @Cacheable | 触发将数据保存到缓存的操作(启动缓存) |
| @CacheEvict | 触发将数据从缓存删除的操纵(失效模式) |
| @CachePut | 不影响方法执行更新缓存(双写模式) |
| @Caching | 组合以上多个操作(点击注解看源码就知道了,组合注解)) |
| @CacheConfig | 在类级别共享缓存的相同配置 |
3.2. 原理梳理
3.2.1. 比较重要的源码类
CacheAutoConfiguration缓存的自动配置- 用的类型是 redis 所以看
RedisCacheConfiguration CacheManager缓存管理者- 类型是
redis所以看RedisCacheManager CacheProperties缓存默认配置- idea 搜索的方法 双击
shift或者ctrl n
3.2.2. 原理说明
java
//流程说明:
// CacheAutoConfiguration => RedisCacheConfiguration =>
// 自动配置了RedisCacheManager => 初始化所有的缓存 =>
// 每个缓存决定使用什么配置=>
// =>如果RredisCacheConfiguration有就用已有的,没有就用默认配置(CacheProperties)
// =>想改缓存的配置,只要给容器中放一个RredisCacheConfiguration即可
// =>就会应用到当前RedisCacheManager管理的所有缓存分区中
3.3. 默认缓存的数据类型
在默认配置下,springcache 给我们缓存的试用 jdk 序列化过的数据
我们通常是缓存 Json 字符串,因为使用 Json 能跨语言,跨平台进行交互,所以我们也可以修改他的默认配置,包括 ttl(过期时间)、存储格式、等...
3.4. 整合
引入依赖
xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
先看下配置源码是怎么样的 RedisCacheConfiguration

创建配置类(照猫画虎)
注意事项:要让原本配置文件的一些配置生效
java
//开启属性绑定配置的功能
@EnableConfigurationProperties(CacheProperties.class)
java
配置文件(application.properties)
properties
#类型指定redis
spring.cache.type=redis
#一个小时,以毫秒为单位
spring.cache.redis.time-to-live=3600000
#给缓存的建都起一个前缀。 如果指定了前缀就用我们指定的,如果没有就默认使用缓存的名字作为前缀,一般不指定
#spring.cache.redis.key-prefix=CACHE_
#指定是否使用前缀
spring.cache.redis.use-key-prefix=true
#是否缓存空值,防止缓存穿透
spring.cache.redis.cache-null-values=true
3.5. 使用
在方法上标注注解就可以
3.5.1 @Cacheable(开启缓存功能)
将查询到的结果存入缓存
注意事项
- 有对应的缓存就不进入方法
@Cacheable并没有单独的失效时间的方法。- 但是可以在
CacheManager配置,在加上自动刷新的功能,但是这样的的操作比较繁琐。如果不设置,只有统一的过期时间很容易导致缓存雪崩的问题
01、有返回缓存
java
02、无返回值,或者返回空,缓存空值
java
3.5.2 @CacheEvict(失效模式)
简单的说:就是你执行了修改/删除的操作,他会将缓存里面数据给清除
第一种、删除单个
java
第二种、删除多个,将整个分区的缓存都清除
好比说 a 下面有 b 和 c 。将 b 和 c 一起删除
所以:同一业务\同一类型缓存的数据要放在同一的分区下面
java
//1、失效模式
//2、allEntries = true 删除分区所有的数据
@CacheEvict(value = "student",allEntries = true)
@GetMapping("/updateCascade")
public void updateCascade(Users users) {
//service的业务代码
System.out.println("更新分区所有的数据");
}
3.5.3 @Caching(组合使用)

比如说要让哪个分区下面的哪个缓存失效(删除)
java
4. 简单实战案例
实体类:
java
public class User {
private String id;//主键
private String userName;//用户名
private String password;//密码
//get /set /构造、tostring...省略
}
java
效果:

5. Spring-Cache 的不足
SpringCache 对读模式都进行处理,解决了缓存击穿,缓存穿透,缓存雪崩的问题,但是对写模式并没有去处理
读模式(SpringCache 都处理了)
- 缓存穿透:查询一个 null 数据。 解决方法:缓存空数据。
spring.cache.redis.cache-null-values=true - 缓存击穿:大量并发进来,查询一个正好过期的数据。 解决方法:加锁:默认是无加锁的;
@Cacheable(sync = true),加锁(解决缓存击穿) - 缓存雪崩:大量的 key 同时过期 解决方法:加随机时间 (很容易弄巧成拙,要注意)
spring.cache.redis.time-to-live=3600000以 ms 为单位 3600000 为 1 小时
写模式(SpringCache 没有管)
我们该如何解决(3 种方式)
- 引入中间件
Canal,感知到mysql的更新去更新 - 读多写多的,直接去数据库查询
6. Spring-Cache 小结
1、对于常规数据(读多写少,及时性、一致性要求不高的数据)完全可以使用 Spring Cache
2、对于特殊数据(比如要求高一致性)则需要特殊处理
✨ 感谢您的耐心阅读!!!!
✨ 文章仅限学习使用~
✨ 感谢耐心阅读!!❤
✨ 文章转载于: ,如有侵权,请联系删除。







评论