当前位置: 首页 > news >正文

Spring-Cache 缓存

1.简介

 2.SpringCache 整合

简化缓存开发

1.导入依赖

 <!-- spring cache        --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-cache</artifactId></dependency>

2.redis 作为缓存场景

  <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency>

3. 配置类

1.自动配置

自动配置了哪些

CacheAutoConfiguration 自动导入 RedisCacheConfiguration

自动配好了缓存管理器  RedisCacheManager

2.手动需要得yaml配置

spring:
#缓存cache:type: redis

3.测试 使用缓存

1.开启缓存
 1.使用redis作为缓存
spring:
#缓存cache:type: redis
@EnableCaching //放在主启动类上
2.使用注解就可以完成缓存
 //每一个需要缓存的数据我们都来指定要放到哪个名字的缓存 [缓存的分区(按照业务类型分区)]@Cacheable({"category"}) //代表这个数据是可以缓存的 当前方法的结果需要缓存,如果缓存中有,方法不用调用。//如果缓存中没有,调用方法,将方法结果放入缓存@Overridepublic List<CategoryEntity> getLevel1Category() {System.out.println("调用了数据库getLevel1Category");List<CategoryEntity> parentCid = this.list(new QueryWrapper<CategoryEntity>().eq("parent_cid", 0));return parentCid;}

    //默认行为//1.如果缓存中有,方法不用调用//2.key默认自动生成 : 缓存的名字::SimpleKey [](自主生产的key的值)//3.缓存的value的值 默认使用JDK序列化机制 将序列化后的数据存入Redis//4.默认ttl时间 -1;

 

//自定义//1. 指定生成的缓存使用的key key 属性指定,接受一个SPEL 表达式  ${}  #{}//2. 指定缓存的过期时间 yml 配置文件中修改ttl//3. 将数据存入JSON标准格式

 

 

    public  String mycategorykey="我自定义的key";@Override
//    @Cacheable(value = {"category"}, key = "'level1Category'")
//    @Cacheable(value = {"category"}, key = "#root.method.name")//root是当前上下文的意思 method 是方法 可以有参数 各种东西@Cacheable(value = {"category"}, key = "#root.target.mycategorykey")//root 可以拿到当前对象 当前方法 当前参数public List<CategoryEntity> getLevel1Category() {System.out.println("调用了数据库getLevel1Category");List<CategoryEntity> parentCid = this.list(new QueryWrapper<CategoryEntity>().eq("parent_cid", 0));return parentCid;}

 2.自定义缓存设置

保存的数据为json格式

1.讲一下 缓存redis配置类的 原理 

CacheAutoConfiguration->RedisCacheConfiguration->自动配置了缓存管理器 RedisCacheManager->初始化所有的缓存->每个缓存觉得使用什么配置->如果RedisCacheConfiguration有在 容器中自己 配置,就要用自己的配置,否则就用默认的配置

所以,我们只需要给容器中放入一个RedisCacheConfiguration即可

就会应用到当前缓存管理器的所有缓存中

2.配置类
package com.jmj.gulimall.product.config;import org.springframework.boot.autoconfigure.cache.CacheProperties;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.StringRedisSerializer;@Configuration
@EnableCaching
public class MyCacheConfig {/*** 给了默认配置文件就不生效了* 因为 条件判断了 if config !=null  就返回* 也不需要加@EnableConfigurationProperties(CacheProperties.class)* 因为默认自动装配类已经加入这个* @return*/@Beanpublic RedisCacheConfiguration redisCacheConfiguration(CacheProperties cacheProperties) {RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig();/*** public RedisCacheConfiguration entryTtl(Duration ttl) { 是new新对象 得要覆盖上* 		return new RedisCacheConfiguration(ttl, cacheNullValues, usePrefix, keyPrefix, keySerializationPair,* 				valueSerializationPair, conversionService);*        }*/config = config.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()));config = config.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer()));CacheProperties.Redis redisProperties = cacheProperties.getRedis();if (redisProperties.getTimeToLive() != null) {config = config.entryTtl(redisProperties.getTimeToLive());}if (redisProperties.getKeyPrefix() != null) {config = config.prefixKeysWith(redisProperties.getKeyPrefix());}if (!redisProperties.isCacheNullValues()) {config = config.disableCachingNullValues();}if (!redisProperties.isUseKeyPrefix()) {config = config.disableKeyPrefix();}return config;}}
#配置数据源
spring:
#缓存cache:redis:time-to-live: 36000000  #单位 ms 先设定一个小时过期时间use-key-prefix: false #不使用,原来的前缀就没有了 key是什么 就是什么key-prefix: CACHE_ #所有Key都设置一个前缀来区分  如果指定了 前缀就用指定的,没有就用默认的 name::[]cache-null-values: true #是否缓存入空值 可以解决缓存穿透type: redis
3.实现 
  //每一个需要缓存的数据我们都来指定要放到哪个名字的缓存 [缓存的分区(按照业务类型分区)]//代表这个数据是可以缓存的 当前方法的结果需要缓存,如果缓存中有,方法不用调用。//如果缓存中没有,调用方法,将方法结果放入缓存//默认行为//1.如果缓存中有,方法不用调用//2.key默认自动生成 : 缓存的名字::SimpleKey [](自主生产的key的值)//3.缓存的value的值 默认使用JDK序列化机制 将序列化后的数据存入Redis//4.默认ttl时间 -1;//自定义//1. 指定生成的缓存使用的key key 属性指定,接受一个SPEL 表达式   #{}//2. 指定缓存的过期时间 yml 配置文件中修改ttl//3. 将数据存入JSON标准格式public  String mycategorykey="我自定义的key";@Override
//    @Cacheable(value = {"category"}, key = "'level1Category'")
//    @Cacheable(value = {"category"}, key = "#root.method.name")//root是当前上下文的意思 method 是方法 可以有参数 各种东西@Cacheable(value = {"category"}, key = "#root.target.mycategorykey")//root 可以拿到当前对象 当前方法 当前参数public List<CategoryEntity> getLevel1Category() {System.out.println("调用了数据库getLevel1Category");//线程不安全,需要加分布式锁和读写锁List<CategoryEntity> parentCid = this.list(new QueryWrapper<CategoryEntity>().eq("parent_cid", 0));return parentCid;}
1.删除缓存
    /*** 级联更新所有关联数据* @param category* @throws Exception*/@CacheEvict(value = "category", key = "'category::tree'")//删除缓存@Override@Transactional(rollbackFor = Exception.class)public void updateDetails(List<CategoryEntity> category) throws Exception {category.stream().filter(c -> StringUtils.isNotBlank(c.getName())).forEach(c -> {List<CategoryBrandRelationEntity> updateList = categoryBrandRelationService.list(new QueryWrapper<CategoryBrandRelationEntity>().eq("catelog_id", c.getCatId())).stream().peek(r -> r.setCatelogName(c.getName())).collect(Collectors.toList());categoryBrandRelationService.updateBatchById(updateList);});this.updateBatchById(category);}
2.存入 
  @Override@Cacheable(value = {"category"}, key = "'category::tree'")public List<CategoryEntity> listWithTree() {//1.查出所有分类System.out.println("三级分类查询数据库");List<CategoryEntity> all = this.list();//2.组装成父子的属性结构List<CategoryEntity> level1Menus = all.stream().filter(c -> c.getParentCid().equals(0L)).map(categoryEntity -> categoryEntity.setChildren(getChildrenCategory(all, categoryEntity.getCatId())))//大于放后面 升序.sorted(Comparator.comparing(CategoryEntity::getSort)).collect(Collectors.toList());return level1Menus;}
3.要操作多个 
//    @CacheEvict(value = "category", key = "'category::tree'")//删除缓存//如果多操作的话@Caching(evict = {@CacheEvict(value = "category", key = "'category::tree'"),@CacheEvict(value = "category", key = "#root.target.mycategorykey")})@Override@Transactional(rollbackFor = Exception.class)public void updateDetails(List<CategoryEntity> category) throws Exception {category.stream().filter(c -> StringUtils.isNotBlank(c.getName())).forEach(c -> {List<CategoryBrandRelationEntity> updateList = categoryBrandRelationService.list(new QueryWrapper<CategoryBrandRelationEntity>().eq("catelog_id", c.getCatId())).stream().peek(r -> r.setCatelogName(c.getName())).collect(Collectors.toList());categoryBrandRelationService.updateBatchById(updateList);});this.updateBatchById(category);}
4.删除这个分区下所有数据 (失效模式)

存储同一类型的数据,都可以指定一个分区

    @CacheEvict(value = "category", allEntries = true)// 设定了 use-key-prefix: false 这个如果没有这个分类,将全部缓存删除
use-key-prefix必须要为true  和  key-prefix: 不设置 这个才有用
5.双写模式

@CachePut//双写模式

3.SpringCache的不足

基本都能解决,唯独缓存击穿特别一点

 这样就是加了个本地锁,本地也就是放一个过来

  @Override@Cacheable(value = {"category"}, key = "'tree'",sync = true)public List<CategoryEntity> listWithTree() {//1.查出所有分类System.out.println("三级分类查询数据库");List<CategoryEntity> all = this.list();//2.组装成父子的属性结构List<CategoryEntity> level1Menus = all.stream().filter(c -> c.getParentCid().equals(0L)).map(categoryEntity -> categoryEntity.setChildren(getChildrenCategory(all, categoryEntity.getCatId())))//大于放后面 升序.sorted(Comparator.comparing(CategoryEntity::getSort)).collect(Collectors.toList());return level1Menus;}

只有cacheable 查询注解的时候 才能够加锁

虽然加的是本地锁,但是一台服务器只能一个访问,也是够用了

4.总结

常规数据(读多写少,即时性,一致性要求不高的数据 ):完全可以使用springcache(写模式:只有数据有过期时间 就完全足够了 这样可以保证数据的最终一致性)

特殊数据 (特殊设计)例如 canal 感知数据库去更新 缓存

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • Zookeeper背景优缺点,以及应用场景
  • 头歌资源库(32)n皇后问题
  • 【坑】微信小程序开发wx.uploadFile和wx.request的返回值格式不同
  • 如何找工作 校招 | 社招 | 秋招 | 春招 | 提前批
  • Docker Compose部署Kafka集群并在宿主机Windows连接开发
  • 对AAC解码的理解
  • Linux C++ 054-设计模式之外观模式
  • leetcode日记(38)字母异位词分组
  • C++数组
  • 【密码学】消息认证
  • 九、Linux二进制安装ElasticSearch集群
  • 【JavaScript】解决 JavaScript 语言报错:Uncaught SyntaxError: Unexpected token
  • Qt QWebSocket网络编程
  • Nginx -Web服务器/反向代理/负载均衡
  • Selenium WebDriver中的显式等待与隐式等待:深入理解与应用
  • [PHP内核探索]PHP中的哈希表
  • Android优雅地处理按钮重复点击
  • docker容器内的网络抓包
  • golang 发送GET和POST示例
  • Netty源码解析1-Buffer
  • 基于游标的分页接口实现
  • 如何选择开源的机器学习框架?
  • 项目实战-Api的解决方案
  • 用Visual Studio开发以太坊智能合约
  • [地铁译]使用SSD缓存应用数据——Moneta项目: 低成本优化的下一代EVCache ...
  • mysql 慢查询分析工具:pt-query-digest 在mac 上的安装使用 ...
  • NLPIR智能语义技术让大数据挖掘更简单
  • # Python csv、xlsx、json、二进制(MP3) 文件读写基本使用
  • (17)Hive ——MR任务的map与reduce个数由什么决定?
  • (done) ROC曲线 和 AUC值 分别是什么?
  • (板子)A* astar算法,AcWing第k短路+八数码 带注释
  • (补)B+树一些思想
  • (第30天)二叉树阶段总结
  • (二)测试工具
  • (二)换源+apt-get基础配置+搜狗拼音
  • (附源码)计算机毕业设计ssm高校《大学语文》课程作业在线管理系统
  • (附源码)计算机毕业设计ssm基于Internet快递柜管理系统
  • (轉貼) 資訊相關科系畢業的學生,未來會是什麼樣子?(Misc)
  • .md即markdown文件的基本常用编写语法
  • .net websocket 获取http登录的用户_如何解密浏览器的登录密码?获取浏览器内用户信息?...
  • .NET 指南:抽象化实现的基类
  • .NET6 命令行启动及发布单个Exe文件
  • .net经典笔试题
  • .NET中两种OCR方式对比
  • /etc/fstab和/etc/mtab的区别
  • @zabbix数据库历史与趋势数据占用优化(mysql存储查询)
  • []T 还是 []*T, 这是一个问题
  • [BZOJ 1032][JSOI2007]祖码Zuma(区间Dp)
  • [BZOJ] 1001: [BeiJing2006]狼抓兔子
  • [CLickhouse] 学习小计
  • [CR]厚云填补_多云条件下土地覆盖分割的多模态多任务学习
  • [Kimi笔记]C# 中,public、private 和 internal 访问修饰符
  • [LeetCode]Balanced Binary Tree
  • [Oh My C++ Diary]内联函数
  • [python]基于opencv实现的车道线检测