一、简介
SpringCache 是Spring 提供的一整套的缓存解决方案,他不是具体的缓存实现,它只提供了一整套的接口和代码规范、配置、注解等,用于整合各种缓存方案。
Spring 从 3.1 开始定义了 org.springframework.cache.Cache 和 org.springframework.cache.CacheManager 接口来统一不同的缓存技术,并支持使用 JCache(JSR-107)注解简化我们开发。
Cache 接口为缓存的组件规范定义,包含缓存的各种操作集合;
Cache 接口下 Spring 提供了各种 xxCache 的实现,如 RedisCache、EhCacheCache、ConsurrentMapCache 等;
- 每次调用需要缓存功能的方法时,Spring 会检查指定参数的指定目标方法是否已经被调用过;如果有就直接从缓存中获取方法调用后的结果,如果没有就调用方法并缓存结果后返回给用户,下次调用直接从缓存中获取。
- 使用 Spring 缓存抽象时我们需要关注以下两点:
-
- 1、确定方法需要被缓存以及它们的缓存策略
- 2、从缓存中读取之前缓存存储的数据
二、使用
2.1 搭建
2.1.1 导入依赖
<!-- 引入 redis -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
<version>2.2.6.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
<version>2.2.6.RELEASE</version>
</dependency>
2.1.2 写配置
- 自动配置了哪些?
CacheAutoConfiguration 会导入 RedisCacheConfiguration;
自动配好了缓存管理器 RedisCacheManager
- 配置使用 redis 作为缓存
spring.cache.type=redis
在启动类上开启注解
@EnableCaching
2.1.3 测试使用缓存
对于缓存声明,Spring的缓存抽象提供了一组Java注解:
- @Cacheable:触发缓存填充。
- @CacheEvict: 触发将数据从缓存删除的操作。(失效模式使用这个注解)
- @CachePut:在不干扰方法执行的情况下更新缓存。(双写模式使用这个注解)
- @Caching:重新组合多个缓存操作以应用于一个方法。
- @CacheConfig:在类级别共享一些常见的缓存相关设置。
@Override
@Cacheable({"xiaolei"}) // 当前方法结果需要缓存,如果缓存有,方法不用调用。如果缓存没有,会调用方法,最后将方法的结果放入缓存
public List<CategoryEntity> getLevel1Categorys() {
System.out.println("进入方法");
List<CategoryEntity> list = baseMapper.selectList(new LambdaQueryWrapper<CategoryEntity>().eq(CategoryEntity::getCatLevel, 1));
return list;
}
2.1.4 Cacheable 细节设置
使用 Cahceable ,默认会有几个行为:
- 1、如果缓存中有,方法不用调用
- 2、key 默认自动生成,缓存的名字为:category::simplekey[](自动生成的 key 值)
- 3、缓存的 value 的值,默认使用 jdk 序列化机制,将序列化后的数据存到 redis
- 4、默认时间: -1 (不符合规范)
我们需要自定义一些需求:
1) 指定生成的缓存使用的 key : key 属性指定,接受一个 spEL.
key 的表达式可以有很多方法动态获取。例如方法名作为缓存key等(#root.method.name)
@Cacheable(value = {"xiaolei"},key = "'sysId:product:category'")
2) 指定缓存的数据的存活时间: 在配置文件中修改
spring.cache.redis.time-to-live=3600000
3) 将数据保存为 json 格式
- cacheAutoConfiguration
- RedisCacheConfiguration
@Configuration
@EnableCaching
@EnableConfigurationProperties(CacheProperties.class) // 开启属性配置的绑定功能
public class MyCacheConfig {
@Autowired
CacheProperties cacheProperties;
/**
* 配置文件中的配置没有用上.
* 原来配置文件绑定的配置类需要让它生效.
* @return
*/
@Bean
RedisCacheConfiguration redisCacheConfiguration(CacheProperties cacheProperties){
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig();
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.prefixCacheNameWith(redisProperties.getKeyPrefix());
}
if (!redisProperties.isCacheNullValues()) {
config = config.disableCachingNullValues();
}
if (!redisProperties.isUseKeyPrefix()) {
config = config.disableKeyPrefix();
}
return config;
}
}
原理:
通过CacheAutoConfiguration 获取到 redisCahceConfiguration,它自动初始化所有的缓存-> 每个缓存决定使用什么配置,就看配置文件中有没有,如果有,就用配置文件中的值,如果没有,就用缓存中的值。-> 想改缓存的配置,只需要给容器中放一个 RedisCacheConfiguration 即可。-> 就会想应用。
2.1.5 cacheing
默认失效模式,即删除缓存。
删除单个 key
@CacheEvict(value = "xiaolei",key = "'sysId:product:category'")
删除多个缓存
@Caching(evict = {
@CacheEvict(value = "xiaolei",key = "'sysId:product:category'"),
@CacheEvict(value = "xiaolei",key = "'sysId:product:category2'")
})
存储同一类型的数据,都可以指定同一个分区。
默认就是分区名作为缓存的名字。
2.2 原理与不足
2.2.1 读模式
- 缓存穿透:查询一个 null 数据。解决:缓存空数据,cache-null-value = true
- 缓存击穿:大量并发进来同时查询一个正好过期的数据。解决:加锁?默认是无加锁的。(sync =true )加锁解决
- 缓存雪崩:大量的 key 同时过期。解决:加随机时间。(加上过期时间就行)
2.2.2 写模式(缓存与数据库一致)
- 读写加锁
- 引入 Cannal:感知 mysql 的更新去更新数据库
- 都多写多:直接去数据库查询就行