代码编织梦想

Spring Cache官网:https://docs.spring.io/spring-framework/docs/5.2.11.RELEASE/spring-framework-reference/integration.html#cache

一、使用

1、引入依赖

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

<dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
</dependency>

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

2、配置

spring:
  # redis配置
  redis:
    host: localhost
    port: 6379
    pool:
      # 连接池最大连接数(使用负值表示没有限制)
      max-active: 8
      # 连接池最大阻塞等待时间(使用负值表示没有限制)
      max-wait: -1
      # 连接池中的最大空闲连接
      max-idle: 8
      # 连接池中的最小空闲连接
      min-idle: 0
    # 连接超时时间(毫秒)
    timeout: 0

  # cache配置	
  cache:
    type: redis

3、开启缓存功能

@EnableCaching
@SpringBootApplication
public class GulimallProductApplication {
	public static void main(String[] args) {
		SpringApplication.run(GulimallProductApplication.class, args);
	}
}

4、测试 @Cacheable

@Cacheable(value={"user"},key = "'userList'")
@GetMapping("/getUserList")
public List<User> getUserList(){
    System.out.println("getUserList....");
    //构造测试数据
    List<User> userList = new ArrayList<>();
    userList.add(new User("张三", 18));
    userList.add(new User("李四", 19));
    userList.add(new User("王五", 20));
    return userList;
}

@Cacheable 注解说明

缓存当前方法的返回的结果, 如果缓存中有,不调用方法,直接返回;如果缓存中没有,调用方法,最后将方法的结果放入缓存;

默认行为

  • 缓存的value值,默认使用jdk序列化机制,将序列化的数据存到redis中;

  • key是默认生成的,如果不指定,默认user::SimpleKey [];可以通过key属性指定,接收一个SpEL表达式的值;

  • 默认时间是 -1;可以在配置文件中配置

    # 缓存存活时间,单位:毫秒
    spring.cache.redis.time-to-live=360000
    

@Cacheable 属性说明

5、访问

访问接口:localhost:8080/list
第一次打印 getUserList....;查看redis缓存数据,redis中已经存入user的数据;
第二次直接走缓存,不调用方法,不打印;

二、自定义缓存配置

缓存中的value默认是用jdk序列化,不方便的我们查看,修改成json序列化;

自定义配置类

@EnableCaching //开启缓存启动类的注解从启动类移到这里
@Configuration
public class MyCacheConfig {

    @Bean
    public RedisCacheConfiguration redisCacheConfiguration() {

        RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig();

        //key的序列化 string
        config = config.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()));
        //value的序列化  json
        config = config.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer()));

        return config;
    }
}

测试(同上),缓存中的value已经用json序列化,但过期时间变成 -1;也就是说配置文件中的值失效了!

[
  "java.util.ArrayList",
  [
    {
      "@class": "com.gulimall.product.entity.User",
      "name": "张三",
      "age": 18
    },
    {
      "@class": "com.gulimall.product.entity.User",
      "name": "李四",
      "age": 19
    },
    {
      "@class": "com.gulimall.product.entity.User",
      "name": "王五",
      "age": 20
    }
  ]
]

配置文件失效问题

原因:源码中缓存配置类CacheProperties并没有放在容器中我们需要开启属性配置绑定功能:@EnableConfigurationProperties(CacheProperties.class),然后拿到配置文件的值赋值到属性中;可以参考源码RedisCacheConfiguration类的createConfiguration方法;

@EnableConfigurationProperties(CacheProperties.class) //开启属性配置绑定功能
@EnableCaching  //开启缓存启动类的注解从启动类移到这里
@Configuration
public class MyCacheConfig {

    @Bean
    public RedisCacheConfiguration redisCacheConfiguration(CacheProperties cacheProperties) {

        RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig();
        // config = config.entryTtl();

        //key的序列化 string
        config = config.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()));
        //value的序列化  json
        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;
    }
}

拿到容器中的CacheProperties对象,可以像上面一样通过方法参数拿到,也可以通过注入一个属性拿到;

@Autowired
public CacheProperties cacheProperties;

三、Spring Cache 注解

  • @Cacheable: 将数据保存到缓存;
  • @CacheEvict: 将数据从缓存删除;失效模式
  • @CachePut: 不影响方法执行更新缓存;双写模式
  • @Caching: 组合多个缓存操作;
  • @CacheConfig: 在类级别共享缓存的相同配置;

@Cacheable

添加到缓存

@Cacheable(value={"user"},key = "'userList'")
@GetMapping("/getUserList")
public List<User> getUserList(){
    System.out.println("getUserList....");
    //构造测试数据
    List<User> userList = new ArrayList<>();
    userList.add(new User("张三", 18));
    userList.add(new User("李四", 19));
    userList.add(new User("王五", 20));
    return userList;
}

@CacheEvict

删除缓存,【失效模式】

@CacheEvict(value={"user"},key = "'userList'")
@PostMapping("/addUser")
public User addUser(@RequestBody User user){
    System.out.println("addUser....");
    return user;
}

删除user分区的所有属性:@CacheEvict(value={"user"}, allEntries = true)

@CachePut

根据返回值更新缓存,【失效模式】

@CachePut(value={"user"}, key = "'userList1'")
@PutMapping("/updateUser")
public List<User> updateUser(){
    System.out.println("updateUser....");
    //构造测试数据
    List<User> userList = new ArrayList<>();
    userList.add(new User("1","张三", 18));
    return userList;
}

@Caching

组合多个缓存操作;

//同时删除user::userList1 和user::userList2 缓存
@Caching(evict = {
    @CacheEvict(value={"user"},key = "'userList1'"),
    @CacheEvict(value={"user"},key = "'userList2'")
})
@GetMapping("/caching")
public String caching(){
    System.out.println("caching....");
    return "caching ok";
}

四、Spring-Cache的不足之处

读模式

  • 缓存穿透:查询一个null数据;
    解决方案:缓存空数据;
  • 缓存击穿:大量并发进来同时查询一个正好过期的数据;
    解决方案:默认是无加锁的;使用@Cacheable(sync = true)来解决击穿问题;
  • 缓存雪崩:大量的key同时过期;
    解决方案:加过期时间;

读模式的3个问题spring Cache都考虑到了;

写模式:(缓存与数据库一致)

  • 读写加锁;
  • 引入Canal,感知到MySQL的更新去更新Redis;
  • 读多写多,直接去数据库查询就行;

写模式spring Cache 没做特殊处理,根据特殊业务进行特殊处理!

总结

  • 常规数据(读多写少,即时性,一致性要求不高的数据,完全可以使用Spring-Cache)
    写模式(只要缓存的数据有过期时间就足够了);
  • 特殊数据:特殊设计。

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。 本文链接: https://blog.csdn.net/qq_33732195/article/details/111094569

Redis笔记(六)之Jedis和Springboot整合-爱代码爱编程

Jedis 什么是Jedis 是 Redis 官方推荐的 java连接开发工具! 使用Java 操作Redis 中间件!如果你要使用 java操作redis,那么一定要对Jedis 十分的熟悉! 我们要使用 Java 来操作 Redis,知其然并知其所以然,授人以渔! 学习不能急躁,慢慢来会很快! 加油!!^_^ 测试 1,导入

yum install redis-爱代码爱编程

直接yum 安装的不是最新版本 yum install redis 如果要安装最新的redis,需要安装Remi的软件源,官网地址:http://rpms.famillecollet.com/ yum install -y http://rpms.famillecollet.com/enterprise/remi-release-7.rpm 然后可

《Redis设计与实现》之字典-爱代码爱编程

字典:用于保存键值对(key-value pair)的抽象数据结构 在字典中,一个键和一个值进行关联,建立之间的映射关系 字典作为一种数据结构内置于很多高级编程语言中,但是Redis所使用的C语言并没有内置这种数据结构,所以Redis构建了自己的字典实现。 1.字典的实现 Redis的字典底层是使用哈希表实现的,一个哈希表里面有多个哈希表节点,每个节点

SpringBoot2.x整合Redis数据库-爱代码爱编程

1、Redis是当下最流行的用于实现缓存机制的NoSQL数据库,其主要通过key-value存储,支持高并发访问。在实际工作中,Redis结合SpringData技术后可以方便地实现序列化对象的存储。SpringBoot很好地支持了Redis,可以在项目中使用SpringData进行Redis数据操作。   SpringBoot整合RedisTempla

Canal+Kafka实现mysql与Redis数据同步-爱代码爱编程

Canal+Kafka实现mysql与Redis数据同步 一、Canal简介 canal主要用途是基于 MySQL 数据库增量日志解析,提供增量数据订阅和消费,早期阿里巴巴因为杭州和美国双机房部署,存在跨机房同步的业务需求,实现方式主要是基于业务 trigger 获取增量变更。从 2010 年开始,业务逐步尝试数据库日志解析获取增量变更进行同步,由此衍

redis 哨兵机制-爱代码爱编程

问题: 当主节点master挂了怎么办? 答:需要设置一个哨兵,如果检测到master挂了,那么将其中一台的从节点设置为主节点 如何配置: 安装目录中有一个sentinel.conf 文件,可以将此文件进行拷贝进行修改 拷贝文件去其他目录下 cp sentinel.conf /usr/local/redis sentinel.conf 文件 da

尚硅谷笔记Springboot《2》——Spring的配置文件-爱代码爱编程

配置文件 application.propertiesapplication.yum——yuml是一个标记语言标记语言 以前的配置文件,大多是以xml,使用yaml的配置文件以数据作为中心,比json,xml更适合作为配置文件。可以在IDEA中自行创建yaml配置文件,与appli放ation.properties放在同一个文件夹下。 基本语法 语

编译Spring源码的步骤及一些问题-爱代码爱编程

编译Spring源码的步骤及问题 步骤下载对应工具编译部分测试其他工程引入自己编译的源码碰到的问题小结 步骤 下载对应工具 1.下载gradle,使用下载好的gradle进行编译,不需要太新,但是版本一定要匹配(好像没碰到版本冲突问题,注意一下就得了)。 gradle网址:https://services.gradle.org/distr

Spring中毒太深,离开Spring我居然连最基本的接口都不会写了-爱代码爱编程

Spring中毒太深,离开Spring我居然连最基本的接口都不会写了 前言Spring 能帮我们做什么控制反转(IOC)依赖注入(DI)面向切面编程(AOP)利用 Spring 来完成 Hello World假如没有了 Spring基于 Servlet 开发模仿Spring总结 前言 随着 Spring 的崛起以及其功能的完善,现在可能绝大部

Spring事务三:cglib方式代理,this调用不生效本质-爱代码爱编程

基于自己之前对Spring代理生成的认知,大致的认为采用cglib框架生成目标类的子类;在父类方法体内使用this应该也是调用代理类的实例,那么容器应该能感知方法上的注解进而执行拦截(比如事务拦截器),但是事与愿违用this调用的没有让事务拦截生效。 先来看我的定势思维想象:假设有父类F和子类S public class F { pu

基于SSM的电影院订票系统(Spring+SpringMVC+Mybatis)简洁版-爱代码爱编程

运行环境,jdk1.8或者jdk1.7、tomcat8或者tomcat8.5、mysql5.7、eclipse或者myeclipse开发环境。使用框架Spring+SpringMVC+Mybatis。 1、基于SSM的电影院订票系统简洁版(Spring+SpringMVC+Mybatis),登录界面,如下所示: 2、基于SSM的电影院订票系统简洁版

基于子类的动态代理-爱代码爱编程

故事背景: 以前,生产商生产电脑卖给消费者,生产商直接跟消费者对接。 现在,生产商先把电脑卖给经销商,经销商再把电脑卖给消费者,消费者付给经销商1000元,经销商要从中拿走20%,剩余的钱再给生产商 第一步,创建生产者实体类 /** * 一个生产者 */ public class Producer { /** * 销售