前言
缓存相信各位都不陌生,作为开发中非常重要的一个组件。是解决高并发与数据库压力的第一方案,目前使用最多的是 redis 缓存中间件。但对于一些 CRUD 的缓存,还是有一些人使用的是 redisTemplate。开发中经常重复造车轮,以至于没有太多的时间去理解业务和逻辑。而 SpringCache 就出现了。只需要一行注解,几个配置,缓存的任务就完成了。我们只需要专心写逻辑就可以。使用也十分简单。本次就 SpringCache 结合当下最常用的 redis 来实践理解一下
SpringCache 的注解
首先是三个常用注解
@Cacheable:获取缓存,如果获取不到缓存则执行本方法 将方法的返回值缓存到redis
@CachePut:更新缓存,无论缓存是否存在,都会执行本方法,并且更新到redis
@CacheEvict:删除缓存,缓存存在则删除,不存在自然不会删
下面来看看 Cacheable 注解中的值
cacheNames:可以理解为缓存容器的名字,也就是缓存要放进的地方
key:理解为redis的key就可以 表示缓存名字
keyGenerator:缓存key的生成策略 后面会详细说明
cacheManager:指定缓存容器,要从哪个缓存容器获取缓存
cacheResolver:管理keyManager的东西
condition:el表达式,写入一个条件,方法执行前判断, 为true则存入缓存 false不会存入
unless:el表达式,写入一个条件 方法执行完成后判断,为true不会写入缓存 false会存入(可用于判断返回值中的内容)
sync: 是否开启同步 设置为true可以避免缓存击穿等问题
以上参数中,key 和 keyGenerator 只选其一,cacheResolver 和 cacheResolver 只选其一 (这两个一般也不会去设置)
依赖与配置文件
依赖只需要引入 redis 和 springCache 的依赖就可以
1 2 3 4 5 6 7 8 9 10 11
| <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> <version>2.7.5</version> </dependency>
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-cache</artifactId> <version>2.7.5</version> </dependency>
|
yml 这里就更简单,只需要将 type 设置为 redis 就可以了
1 2 3 4 5
| server: port: 8080 spring: cache: type: redis
|
代码实践
@Cacheable 的使用
我们需要在启动类上 @EnableCaching
来开启缓存
写一个方法,逻辑为如果查询到了缓存则获取缓存的内容,没有则执行方法 然后存入缓存
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
|
@GetMapping("getUserAble/{id}") @Cacheable(cacheNames = "user", key = "'-' + #userId", sync = true) public User getUserByIdAble(@PathVariable("id") Integer userId) { System.out.println("进入方法"); HashMap<Integer, User> map = new HashMap<>(); map.put(1,new User("张三", 17, "画画")); map.put(2,new User("李四", 28, "听音乐")); map.put(3,new User("王五", 26, "旅游")); map.put(4,new User("赵六", 30, "学习")); return map.get(userId); }
|
确保 redis 中目前没有缓存,然后我们执行方法,第一次控制台打印 [进入方法] 表名没有获取到缓存
之后我们继续访问,则不会继续打印,表名我们获取到的是缓存里的内容。其中,最外层的 user 是 cacheNames。key 则为 cacheNames + “::” + 设置的对应 key,也就是图中的 user::-1
自定义 keyGenerator
我们可以会觉得直接在注解上设置 key,会有些死板,这个时候我们可以选择自定义 keyGenerator 来设置 key 的生成策略
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| @Configuration public class UserKeyGenerator {
@Bean("UserKeyGenerator") public KeyGenerator createUserKeyGenerator() { return new KeyGenerator() { @Override public Object generate(Object target, Method method, Object... params) { System.out.println(target); System.out.println(method); System.out.println(params); Object[] arr = params; return method.getName() + "-" + arr[0]; } }; } }
|
target 是我们的类地址,method 为方法,params 则是参数。这里我使用方法名 + 第一个参数作为 key

在方法上设置 keyGenerator 的值选择我们的 key 生成策略
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
|
@GetMapping("getUserKeyGenerator/{id}") @Cacheable(cacheNames = "user", keyGenerator = "UserKeyGenerator", sync = true) public User getUserByIdKeyGenerator(@PathVariable("id") Integer userId) { System.out.println("进入方法"); HashMap<Integer, User> map = new HashMap<>(); map.put(1,new User("张三", 17, "画画")); map.put(2,new User("李四", 28, "听音乐")); map.put(3,new User("王五", 26, "旅游")); map.put(4,new User("赵六", 30, "学习")); return map.get(userId); }
|
和第一个 key 可以对比以下,key 变成了我们刚刚设置的格式
@CachePut
相对于 Cacheable,put 则要简单得多。不管有没有缓存,都会执行方法,存入 redis,每一次执行都会更新缓存内容。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
|
@PutMapping("getUserPut/{id}") @CachePut(cacheNames = "user", keyGenerator = "UserKeyGenerator") public User getUserByIdPut(@PathVariable("id") Integer userId) { System.out.println("进入方法"); HashMap<Integer, User> map = new HashMap<>(); map.put(1,new User("张三", 17, "画画")); map.put(2,new User("李四", 28, "听音乐")); map.put(3,new User("王五", 26, "旅游")); map.put(4,new User("赵六", 30, "学习")); return map.get(userId); }
|
可以看到,我们点击几次,就执行方法几次,redis 自然也是一直被更新的状态

@CacheEvict
删除缓存,执行该方法会删除对应的缓存。这里就不演示了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
|
@DeleteMapping("getUserEvict/{id}") @CacheEvict(cacheNames = "user", key = "'-' + #userId") public User getUserByIdExict(@PathVariable("id") Integer userId) { System.out.println("进入方法"); HashMap<Integer, User> map = new HashMap<>(); map.put(1,new User("张三", 17, "画画")); map.put(2,new User("李四", 28, "听音乐")); map.put(3,new User("王五", 26, "旅游")); map.put(4,new User("赵六", 30, "学习")); return map.get(userId); }
|
下面我们来说说 unless 和 condition 这两个条件
unless
unless 是方法执行完成之后,判断条件是否为 true 为 true 不会存入缓存。可以使用 result 来表示返回值
例如这里设置的是如果查询的用户姓名为王五 则不会存入缓存
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
|
@PostMapping("getUserUnless/{id}") @Cacheable(cacheNames = "user", key = "'-' + #userId", unless = "#result.name.equals('王五')") public User getUserByIdUnless(@PathVariable("id") Integer userId) { System.out.println("进入方法"); HashMap<Integer, User> map = new HashMap<>(); map.put(1,new User("张三", 17, "画画")); map.put(2,new User("李四", 28, "听音乐")); map.put(3,new User("王五", 26, "旅游")); map.put(4,new User("赵六", 30, "学习")); return map.get(userId); }
|
我们查询王五和赵六,查询完成后只有赵六被存入了 redis 条件实现
condition
condition 同样也是 el 表达式,不同的是,condition 是在方法执行前判断的。为 true 会存入缓存
例如这里设置的是查询用户 id 大于 2 的才会存入缓存
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
|
@GetMapping("getUserCondition/{id}") @Cacheable(cacheNames = "user", key = "'-' + #userId", condition = "#userId > 2", sync = true) public User getUserByIdCondition(@PathVariable("id") Integer userId) { System.out.println("进入方法"); HashMap<Integer, User> map = new HashMap<>(); map.put(1,new User("张三", 17, "画画")); map.put(2,new User("李四", 28, "听音乐")); map.put(3,new User("王五", 26, "旅游")); map.put(4,new User("赵六", 30, "学习")); return map.get(userId); }
|
我们查询 id 为 1-4 的用户
自然只有 3 和 4 被存入了缓存
看到这里,SpringCache 的基本使用就已经完成了。更高阶的操作例如自定义缓存管理器 cacheManager,可以实现存入时间的设置。