天天看点

Spring Cache - Redis 的使用 自定义缓存配置

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 属性说明
  • value:缓存的分区,指定缓存存储的集合名,可按照业务类型分,同属性 cacheNames;

    如果指定缓存前缀

    spring.cache.redis.key-prefix=CACHE_

    @Cacheable(value={"user"})

    中的 value会失效!
  • key:缓存对象存储在Map集合中的key值,缺省按照函数的所有参数组合作为key值,若自己配置需使用SpEL表达式;

    例如:

    key = "#root.method.name"

    表示用方法名作为key

    参考:https://docs.spring.io/spring-framework/docs/5.2.11.RELEASE/spring-framework-reference/integration.html#cache-spel-context

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)

    写模式(只要缓存的数据有过期时间就足够了);

  • 特殊数据:特殊设计。

继续阅读