#|Spring分布式缓存

什么是分布式缓存 在实际开发场景中,往往单机应用无法满足当前的需求,需要对项目进行分布式部署,由此每个项目中的缓存都是属于自己独立服务的,并不能共享,其次当某个服务更新了缓存,其他服务并不知道,当用户请求到其他服务时,获取到的往往还是旧的数据。
这时就需要将缓存的数据放在一个统一的地方进行管理,如:redis
注解介绍 Spring为我们提供了三大注解@Cacheable@CachePut@CacheEvict可在绝大部分场景下优雅实现分布式缓存。
@EnableCaching(启用缓存)
一般注解在启动类或配置类上,表示启用缓存,使@Cacheable@CachePut@CacheEvict等注解生效。
@Cacheable(添加/获取缓存)

属性名 作用
cacheNames/value 缓存名(必须指定至少一个值),即缓存命名空间名称,用于确定缓存目标。
key 缓存key,缺省为按照方法的所有入参进行组合,可以使用SpEL表达式,实际存储key时默认以cacheNames/value作为前缀。
keyGenerator 指定key的生成器生成键值key(与key二选一),非必需。
cacheManager 指定缓存管理器(例如ConcurrentHashMap、Redis等),非必需。
cacheResolver 和cacheManager作用一样,使用时二选一,非必需。
condition 指定缓存的条件(对参数判断,满足什么条件时才缓存),可用SpEL表达式,例如:方法入参为对象user则表达式可以写为condition = "#user.age>18",表示当入参对象user的属性age大于18才进行缓存。
unless 否定缓存的条件(对结果判断,满足什么条件时不缓存),即满足unless指定的条件时,对调用方法获取的结果不进行缓存,例如:unless = "result==null",表示如果结果为null时不缓存。
sync 是否使用异步模式进行缓存,默认false。
cache_test作为缓存名,参数id作为缓存key,如果命中缓存,直接返回结果。如果缓存不存在,就执行方法逻辑,并将方法的返回结果缓存。
@Cacheable(value = "https://www.it610.com/article/cache_test", key = "#id") @GetMapping("/get") public Result get(Long id) { return R.success(jdbcDAO.get(id)); }

@CachePut(更新缓存)
属性名 作用与描述
cacheNames/value 缓存名(必须指定至少一个值),即缓存命名空间名称,用于确定缓存目标。
key 缓存key,缺省为按照方法的所有入参进行组合,可以使用SpEL表达式,实际存储key时默认以cacheNames/value作为前缀。
keyGenerator 指定key的生成器生成键值key(与key二选一),非必需。
cacheManager 指定缓存管理器(例如ConcurrentHashMap、Redis等),非必需。
cacheResolver 和cacheManager作用一样,使用时二选一,非必需。
condition 指定缓存的条件(对参数判断,满足什么条件时才缓存),可用SpEL表达式,例如:方法入参为对象user则表达式可以写为condition = "#user.age>18",表示当入参对象user的属性age大于18才进行缓存。
unless 否定缓存的条件(对结果判断,满足什么条件时不缓存),即满足unless指定的条件时,对调用方法获取的结果不进行缓存,例如:unless = "result==null",表示如果结果为null时不缓存。
cache_test作为缓存名,参数id作为缓存key,当方法逻辑执行完之后,将返回结果进行覆盖缓存。
@CachePut(value = "https://www.it610.com/article/cache_test", key = "#userGroupIPO.id") @PutMapping("/put") public Result put(@Validated UserGroupIPO userGroupIPO) { jdbcDAO.updateById(Convert.toJSONObject(userGroupIPO)); return R.success(jdbcDAO.get(userGroupIPO.getId())); }

@CacheEvict(删除缓存)
属性名 作用与描述
cacheNames/value 缓存名(必须指定至少一个值),即缓存命名空间名称,用于确定缓存目标。
key 缓存key,缺省为按照方法的所有入参进行组合,可以使用SpEL表达式,实际存储key时默认以cacheNames/value作为前缀。
keyGenerator 指定key的生成器生成键值key(与key二选一),非必需。
cacheManager 指定缓存管理器(例如ConcurrentHashMap、Redis等),非必需。
cacheResolver 和cacheManager作用一样,使用时二选一,非必需。
condition 指定删除缓存的条件(对参数判断,满足什么条件时才删除缓存),可用SpEL表达式,例如:入参为字符userId的方法删除缓存条件设定为当入参不是user001就删除缓存,则表达式可以写为condition = "!('user001').equals(#userId)"
allEntries allEntries是布尔类型的,用来表示是否需要清除缓存中的所有元素。默认值为false,表示不需要。当指定allEntries为true时,Spring Cache将忽略指定的key,清除缓存中的所有内容。
beforeInvocation 清除操作默认是在对应方法执行成功后触发的(beforeInvocation = false),即方法如果因为抛出异常而未能成功返回时则不会触发清除操作。使用beforeInvocation属性可以改变触发清除操作的时间。当指定该属性值为true时,Spring会在调用该方法之前清除缓存中的指定元素。
cache_test作为缓存名,参数id作为缓存key,当方法逻辑执行完之后,删除缓存。
@CacheEvict(value = "https://www.it610.com/article/cache_test", key = "#id") @DeleteMapping("/delete") public Result delete(@RequestParam("id") Long id) { jdbcDAO.deleteLogic(id); return R.success(); }

@CacheConfig
注解在类上面,可使@Cacheable@CachePut@CacheEvict注解无需定义重复的:cacheNames/value、keyGenerator、cacheManager、cacheResolver属性值
@CacheConfig(cacheNames = "cache_test") @RestController @RequestMapping("/cache") public class CacheController {@Autowired JdbcDAO jdbcDAO; @Cacheable(key = "#id") // 无需重复指定 cacheNames/value 属性 @GetMapping("/get") public Result get(Long id) { return R.success(jdbcDAO.get(id)); }}

@Caching
用于将@Cacheable、@CachePut、@CacheEvict这三个注解所提供的能力自由组合,如查询用户时缓存不应该只放id → user,应该连同 cellphone → user、email → user一起放入,这样下次如果按照cellphone或email来查询时,也可从缓存中命中了。
@Caching( cacheable = { @Cacheable(value = "https://www.it610.com/article/cache_test", key = "#id") }, put = { @CachePut(value = "https://www.it610.com/article/cache_test", key = "#result.cellphone", condition = "#result != null"), @CachePut(value = "https://www.it610.com/article/cache_test", key = "#result.email", condition = "#result != null") } ) public UserDO getUserDO(Long id) { return jdbcDAO.get(id); }

开始使用 引入依赖
引入spring-boot-starter-data-redis依赖:
org.springframework.boot spring-boot-starter-data-redis

启用缓存
在启动类中加上@EnableCaching注解开启缓存
@EnableCaching @SpringBootApplication public class TestApplication {public static void main(String[] args) throws Exception { SpringApplication.run(TestApplication.class, args); }}

使用缓存
@Cacheable(value = "https://www.it610.com/article/cache_test", key = "#id") @GetMapping("/get") public Result get(Long id) { System.out.println("未命中Redis缓存,使用JDBC去数据库查询数据。"); return R.success(jdbcDAO.get(id)); }

测试
http://localhost:8080/cache/get?id=18
第一次访问-控制台打印结果:
2021-06-05 19:15:34.041INFO 44320 --- [nio-8080-exec-1] ai.yue.library.test.aspect.HttpAspect: requestIp=0:0:0:0:0:0:0:1 2021-06-05 19:15:34.041INFO 44320 --- [nio-8080-exec-1] ai.yue.library.test.aspect.HttpAspect: requestUri=/cache/get 2021-06-05 19:15:34.041INFO 44320 --- [nio-8080-exec-1] ai.yue.library.test.aspect.HttpAspect: requestMethod=GET 2021-06-05 19:15:34.041INFO 44320 --- [nio-8080-exec-1] ai.yue.library.test.aspect.HttpAspect: requestHandlerMethod=ai.yue.library.test.controller.data.redis.CacheController.get() 未命中Redis缓存,使用JDBC去数据库查询数据。 2021-06-05 19:54:11.332 DEBUG 44320 --- [nio-8080-exec-4] druid.sql.Statement: {conn-310002, pstmt-320001} executed. select * from `user` where 1 = 1 and `id` = 18

第一次访问-响应数据:
{ "code": 200, "msg": "成功", "flag": true, "count": null, "data": { "id": 18, "sortIdx": null, "deleteTime": 0, "createTime": "2018-07-18 09:10:46", "updateTime": "2021-06-03 14:29:41", "cellphone": "185****6311", "email": null, "password": "***********************************************", "nickname": "张三丰3", "sex": "男" } }

第二次访问:结果与第一次相同,但未打印出查询日志,因此证明响应的结果是取的Redis缓存数据,而不是执行的JDBC查询。同时我们也确认下Redis中是否有缓存数据:
#|Spring分布式缓存
文章图片

维护缓存
上面我们介绍了如何使用@Cacheable注解添加与获取缓存,实际场景中我们还需要更新缓存@CachePut与删除缓存@CacheEvict
开发者需要结合业务情况,在需要操作到缓存相关数据时,进行缓存数据同步,也就是更新或删除缓存,需求多变灵活运用。
扩展资料
【#|Spring分布式缓存】示例源码

Redis缓存雪崩、击穿、穿透、预热、降级详解

    推荐阅读