前言
缓存相信各位都不陌生,作为开发中非常重要的一个组件。是解决高并发与数据库压力的第一方案,目前使用最多的是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,可以实现存入时间的设置。